Stop modal from closing in the presence of an error

My approach involves using a generic method where, upon adding a food item, a modal window with a form opens for the user to input their details. However, since backend validation for duplicate items can only be retrieved after the API call completes.

I want the modal to remain open in case of errors, as I don't want users to re-enter all their details again.

Below is my code. It includes third-party libraries for handling modals and toast messages.

In the file food.component.ts:

addFood(data: any){
    this.api.request('foodItem', 'POST', data).subscribe({
        next: (response: any) => {
            if (response.message == 'Added Successfully.') {
                this._toastService.showSuccess('Food added.');
            } else {
                this._toastService.showError(response.message);
            }
        },
        complete: () => this.getFood(),
        error: (error: any) => this._toastService.showError(error.message)
    });
}

In the file food-component.html:

 <app-custom-table [tableData]="tableData" [form]="formData" [columnArray]="columnArray" (onAdd)="addFood($event)" (onEdit)="editFood($event)"></app-custom- 
 table>

In custom-table.component.html:

<button type = "button" class="btn btn-link"(click) = "openModal()"[disabled] = "isRowSelected"><span>Add</span></button>
<button type="button" class="btn btn-link"(click) = "openModal(selectedRow)"[disabled] = "isRowNotSelected"></i><span>Edit</span></button>

In custom-table.component.ts:

openModal(rowData: any = null) {
    const config: Partial<DynamicFormComponent> = {
        form: this.form,
        data: rowData
    };

    // more code would go here

In dynamic-form.html:

 @Output() submitSub: Subject<any> = new Subject<any>();
 constructor(
  private activeModal: NgbActiveModal
 ){ 
 }

  onSubmit(){
  if(this.dynamicFormGroup.valid){
    this.submitSubject.next(this.dynamicFormGroup.value)  
   // not sure about the following line
   this.activeModal.close(this.dynamicFodmGroup.value)
  }


  }

To resolve this issue, refer to the latest update in the code snippet above that attempts to deal with the problem related to reading properties of undefined during runtime.

Answer №1

You have the ability to prevent the hide.bs.modal event from occurring if certain conditions are met.

$("#exampleModal").on("hide.bs.modal", function(event) {
  if (!document.querySelector("#defaultCheck1").checked) {
    event.preventDefault();
  }
});
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d2b8a3a7b7a0ab92e1fce7fce3">[email protected]</a>/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cdafa2a2b9beb9bfacbd8df9e3fbe3ff">[email protected]</a>/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="31535e5e45424543504171051f071f03">[email protected]</a>/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
  Launch demo modal
</button>

<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <div class="form-check">
          <input class="form-check-input" type="checkbox" value="" id="defaultCheck1">
          <label class="form-check-label" for="defaultCheck1">
    Allow Closing
  </label>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>

      </div>
    </div>
  </div>
</div>

Answer №2

To avoid using the "close" function, incorporate a Subject into your modal component and subscribe to it when opening. The responsibility of closing the popup lies with the component that opened it.

//inside your modal component
    @Input() form: FormGroup;
    submit:Subject<any>=new Subject<any>()

//in .html
<div class="modal-header">
  <h4 class="modal-title"gt;Hi there!</h4>
  <button type="button" class="btn-close" aria-label="Close"
           (click)="activeModal.dismiss()"
  ></button>
</div>
<div class="modal-body">
  <p>Hello, {{ name }}!</p>
  <!--A simple form example-->
  <div [formGroup]="form">
    <input formControlName="name" />
  </div>
</div>
<div class="modal-footer">
  <button
    type="button"
    class="btn btn-outline-dark"
    <!--only emit one value to the "submit" subject -->
    (click)="submit.next(form.value)"
  >
    Close
  </button>
</div>

For opening:

  open() {
    const modalRef = this.modalService.open(NgbdModalContent);
    modalRef.componentInstance.name = 'World';
    modalRef.componentInstance.form = this.form;
    modalRef.componentInstance.submit
      .pipe(takeUntil(modalRef.closed))
      .subscribe((data: any) => {
        //Utilize a service that returns true or false
        this.dataService.checkData(data.name).subscribe((res: any) => {
          if (res) modalRef.close(); //this closes the popup
        });
      });
  }

View a stackblitz for reference (popup closes only if you input "Angular" as the name)

Tip: Add details on why the "form" is invalid or consider implementing a custom async validator.

Update: In ngb-popup, subscription can be done within the "show" function:

this.ModalService.show((DynamicFormComponent), config, options).subscribe(...)

Alternatively, store the reference in a variable and then subscribe:

//obtain a reference to the "popup"
const modalRef = this.ModalService.show((DynamicFormComponent), config, options)

//Subscribe to it
modalRef.subscribe(...)

Prefer using the second method mentioned above.

If we define a public variable in our "DynamicFormComponent", it becomes accessible. Hence, add the following variable:

submitSubject:Subject<any>=new Subject<any>()

This enables us to subscribe to the Subject:

//use takeWhile to unsubscribe when the modal closes
modalRef.componentInstance.submitSubject.subscribe((data: any) => {}

Avoid using "close" in DynamicFormComponent. Instead, emit a value to the subject:

 //replace in .html
 (click)="activeModal.close(value)"
 //with
 (click)="submitSubject.next(value)"

All code previously under subscription when closing should now be used within the subscription of the subject. Remember, manual closure of the popup is necessary if all conditions are met:

modalRef.close();

Update:

openModal(rowData: any = null) {
    const config: Partial<DynamicFormComponent> = {
        form: this.form,
        data: rowData
    };

    const options: NgbModalOptions = { size: 'sm', centered: true };

    const modalRef = this.ModalService.show((DynamicFormComponent)
                                                  , config, options)

    modalRef.componentInstance.submitSubject.subscribe((data: any) => { 
       //call API to add the foodItem    
       this.api.request('foodItem', 'POST', data).subscribe({
          next: (response: any) => {
            if (response.message == 'Added Successfully.') {
                this._toastService.showSuccess('Food added.');

                //close after adding
                modalRef.close(); 

                //emit update or insert event
                if (rowData) {
                     const updatedRowData = { ...rowData, ...result.Data };
                     this.onEdit.emit(updatedRowData);
                } else { 
                     this.onAdd.emit(result.Data);
                }


            } else {
                this._toastService.showError(response.message);
            }
          },
          complete: () => this.getFood(),
          error: (error: any) => 
           this._toastService.showError(error.message)
      });
  }

Answer №3

Unfortunately, the question is not directly related to ngb-bootstrap but rather a third-party library.

It appears that this library internally uses ngb-bootstrap, so we can attempt the following to obtain the instance:

private ngbmodalService = inject(NgbModal); //<--inject the NgbModal
//or
private ngbmodalService=this.ModalService as NgbModal
private modalRef:any;                    //declare a variable instance

//subscribe to ngbmodalService.activeInstances
//to assign a value to the "modalRef" variable
activeSubscription=this.ngbmodalService.activeInstances.subscribe(res=>{
  this.modalRef=res.length?res[0]:null
})

This way, when opening:

this.ModalService.show(...)
setTimeout(()=>{
this.modalRef.componentInstance.submitSubject.subscribe(...)
})

And when closing:

this.modalRef.close() //<--close all popups

NOTE: To discover the public properties of an Object, you can use something like:

for (let key in this.ModalService) 
console.log(key)

Or simply:

console.log(this.ModalService)

To understand the "properties" of the Object this.ModalService

Update: Another approach using ViewChildren is to declare in our component:

@ViewChildren(DynamicFormComponent) dinamics:QueryList<DynamicFormComponent>
subscription=this.dinamic.changes(res=>{
console.log(res.length,res.first,(res.first as any).submitSubject)
})

We can attempt to access the DynamicFormComponent and the "submitSubject"

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Checking constructor arguments and code style issues

I need to ensure that the constructor parameter is validated correctly when an instance of a class is created. The parameter must be an object that contains exactly all the properties, with the appropriate types as specified in the class definition. If t ...

Disabling ESLint errors is not possible within a React environment

I encountered an eslint error while attempting to commit the branch 147:14 error Expected an assignment or function call and instead saw an expression @typescript-eslint/no-unused-expressions I'm struggling to identify the issue in the code, even ...

Finding the percentage scores for five different subjects among a class

As a beginner in TypeScript, I am still learning the ropes. Here is the code snippet I used to calculate percentage: pere() { this.E=(((+this.English+ +this.Tamil+ +this.Maths+ +this.Science+ +this.Social)/500)*100); console.log(this.E); The result w ...

Using Angular 2 to convert and display data as a particular object type in

I have recently developed a basic application using the Angular2 tutorial as my guide. Initially, I established a straightforward "Book" model: /** * Definition of book model */ export class Book { public data; /** * Constructor for Book ...

A programming element that is capable of accessing a data member, but mandates the use of a setter method for modifications

I am unsure whether I need a class or an interface, but my goal is to create an object with a member variable that can be easily accessed like a regular variable. For example: interface LineRange { begin: number; end: number; } However, I want th ...

What is the best way to create a linear flow when chaining promises?

I am facing an issue with my flow, where I am utilizing promises to handle the process. Here is the scenario: The User clicks a button to retrieve their current position using Ionic geolocation, which returns the latitude and longitude. Next, I aim to dec ...

Flag is activated to retrieve the data from the @Input source

@Input() config= []; flag = false; I need to change the flag to true only when I receive data in the config from the @input. Where should I do this? The data in the config is delayed and I am unable to access it in ngOnInit but can get it in ngOnChanges. ...

Ensuring TypeScript's strict null check on a field within an object that is part of an

When using TypeScript and checking for null on a nullable field inside an object array (where strictNullCheck is set to true), the compiler may still raise an error saying that 'Object is possibly undefined'. Here's an example: interface IA ...

Instead of showing the data in the variable "ionic", there is a display of "[object object]"

Here is the code snippet I'm working with: this.facebook.login(['email', 'public_profile']).then((response: FacebookLoginResponse) => { this.facebook.api('me?fields=id,name,email,first_name,picture.width(720).height( ...

Error when casting Typescript await toPromise

I encountered the following issue: export class FloorManagerComponent implements OnInit { public meta = { list: [], building: Building, loading: true, }; constructor( private router: Router, private ac ...

The data type 'null' is not a valid index type to be used in the Array.reduce() accumulator

This is a follow-up inquiry from: How can JavaScript convert multiple key-value pairs in object lists into one nested object? The initial objective was to merge numerous objects with various key-value pairs into a single nested object. For example, start ...

Using Angular 2, you can pass an object as a parameter to a function

Is there a way to pass an object as a parameter in the DOM on this forum? Within my HTML code, I have the following: <div class="list-items"> <ul> <li *ngFor="let i of item"> <span (click)="onAdd({{newUser.us ...

Unable to make changes to the document

Having trouble updating a document by ID using mongoose and typescript. I'm testing the api and passing the id as a parameter. I've experimented with different methods of updating by ID, but for some reason, it's not working. Can update by ...

Designing a versatile Angular component for inputting data (Mailing Address)

Currently, I am in the process of developing an Angular 11 application that requires input for three distinct mailing addresses. Initially, I thought I had a clear understanding of what needed to be done, only to encounter warnings about elements with non- ...

A data type that exclusively accepts values from an enumerated list without mandating the inclusion of every possible value within the enum

Here's a code snippet I'm working with: enum Foo { a, b, c } type Bar = { [key in keyof typeof Foo]: string; } const test: Bar = { a: 'a', b: 'b' }; I'm encountering an issue where the code is complaining ...

How can you define a function type for a rest parameter in Typescript?

At this point, I have a function that takes in two parameters: param 'a' as a string and 'b' as a function that returns a string. My intention is to call it using a rest parameter and specify the types accordingly. However, on line 10 ...

What is the process for setting up custom global interfaces in TypeScript using .d.ts files?

I'm currently facing an issue in my ReactJS project using Webpack2 and TypeScript. Everything is functioning perfectly except for one thing - I've been struggling to move my self-written interfaces into separate files so they are accessible throu ...

How to access the component instance in Angular through router events

I am currently working on incorporating a title service into my Angular 10 application. My goal is to subscribe to router events, access the activated route's component, check if it has a title() getter, and then use that information to set the page&a ...

What is the best way to retrieve class members using component properties?

I am looking to implement a mixin for setting the header and meta data in my project. I recently discovered vue-meta, which seems to work really well for this purpose. However, I am still getting acquainted with TypeScript and class-based components. How ...

Tips on implementing npm's node-uuid package with TypeScript

Whenever I attempt to utilize node-uuid in TypeScript, I encounter the following issue: Cannot find module uuid This error occurs when I try to import the uuid npm package. Is there a way to successfully import the npm uuid package without encountering ...