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

The Angular2 Mvc5 demonstration application is experiencing compilation errors

I recently started using Visual Studio Enterprise 2015 Update 3 and decided to create a new project called "Angular2 Mvc5 sample application" from the templates available online. However, upon compiling the project, I encountered numerous errors such as: ...

Upon calling set() on Map, the object returned does not conform to a Map data structure

I've been exploring the transition to using immutable.js for managing states: class Register extends Component<{}, Map<string, string>> { state = Map<string, string>(); onInputValueChange(e) { const { name, value } ...

What is the solution for resolving the JavaScript error "TypeError: Cannot read property 'map' of undefined"?

I'm encountering an issue while fetching data from the API and trying to map it to display in a table. The problem is that the fetching process doesn't seem to be working properly, resulting in the state remaining undefined when the page loads. I ...

The error "req.user is not defined" occurs when accessing it from an Android

I am currently collaborating with an Android developer to create an android app. While my colleague handles the front-end development, I focus on the backend work. Specifically, I have implemented the login and authentication features using node.js, expres ...

"Error: Unfinished string literal encountered" occurring in a TypeScript app.component.ts file in NativeScript

I've been trying to learn NativeScript through a tutorial, but I keep encountering errors. Here is an excerpt from my app.component.ts file: import { Component } from '@angular/core'; @Component ({ selector: 'my-app', temp ...

Can we categorize various types by examining the characteristics of an object?

Is it feasible with TypeScript to deduce the result below from the given data: const data = { field1: {values: ['a', 'b', 'c']}, field2: {values: ['c', 'd', 'e'], multiple: true} } const fiel ...

Is there a way to turn off step navigation in bootstrap?

Displayed below is a visual representation of the bootstrap step navigation component. Presently, there is an unseen 'next' button located at the bottom of the page. When this 'next' button is pressed, it transitions from 'step-1 ...

Encountering an issue: Module """ not located at webpackMissingModule

I'm facing an issue while trying to webpack my express application. Specifically, I encounter the following problem whenever I attempt to access the / page: Encountering Error: Cannot find module "." at webpackMissingModule Below is a snippet of c ...

Is there a way to link two HTML files containing jQuery within an HTML index file?

I'm currently working on creating an index page for a pair of HTML files that utilize jquery. Let's break down the structure of these files: Emps1, Emps2: These are two HTML tables that start off empty. TabA_stats, TabB_stats: Each HTML file ha ...

iframe: retrieve current iframe

Currently, I'm working on a page that contains multiple iframes. Only one iframe is active at a time, and I need to be able to determine which one is currently active. I attempted using document.activeElement.id, but it only returns the correct resul ...

Getting the value of a CSS variable under <script> in Vue.js

I am working on implementing a Doughnut chart using chartJs in my application. However, I want to set the color of the chart within the <script> tag and retrieve the color from theme/variables.css. In the current code snippet below, there is a hardc ...

Encountering difficulties with updating customer information in postgreSQL

I am attempting to perform CRUD operations using pg-promises and stored procedures in PostgreSQL. Here is my code: controller.js: const db = require("./../index.js"); exports.getAllData = async (req, res, next) => { try { const data = ...

The Ejs page is failing to render on the simplified code version

Here is the code that displays the 'post' page: app.get("/posts/:postName", function(req, res) { const requestedTitle = _.lowerCase(req.params.postName); posts.forEach(function(post) { const storedTitle = _.lowerCase(post.title ...

Ways to make video controls visible following the initial click on the video

I have a collection of videos on my website and I would like them to display with a poster (thumbnail image) initially. The video controls should only appear once the user clicks on the video for the first time. Therefore, when the page is loaded, the cont ...

Internet Explorer 11 XHR Troubles

Our company has successfully developed a JavaScript video player that can be embedded on various websites using a script tag. As part of the bootstrapping process, we utilize XMLHttpRequest to fetch resources from our server. This creates cross-origin requ ...

Issue encountered: Unable to fetch username and password from request

Currently, I am developing a login and registration system. However, when I input the correct details in my register Post method, the request remains pending and I cannot identify the error. The specific error message it presents is data and salt arguments ...

Using multiple conditions in an angular ngif statement to create a variable

Is it possible to assign the result of a function to a variable in Angular (13) within the .html section of a component, specifically with multiple conditions in ngIf? <div *ngIf="let getMyVar() as myVar && isVisible && isClean" ...

Troubleshooting Angular 2 Typescript: Component not displaying as expected

I am currently in the process of learning Angular 2. Despite encountering numerous error messages, I have successfully set up the routes and can now display the desired component. My next challenge is to incorporate a navbar component into my homepage comp ...

Exploring the use of file loaders with TypeScript

Currently, I have configured a file loader for .png files using esbuild. Additionally, I have the following in my index.d.ts: declare module "*.png" { const value: string; export default value; } One issue I am facing is that my code editor ...

'Without the need to refresh the page, assign a JavaScript variable from JSP on the server side.'

I'm looking for a way to assign a JavaScript variable from JSP without triggering a full page reload. While my current code successfully sets the variable, it also causes the entire page to refresh as a side effect. Here's an example in the exam ...