1

I really have a problem using multiple bootstrap modals within Angular 4. Maybe you can help me out of this. The code is working fine with just one modal. I am not be able to use the buttons/form of the first loaded modal. The second is working fine. After opening and closing the second modal, also the first modal works... strange. Is there something wrong?

<!-- ADD MODAL-->
<div bsModal #addModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myAddModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title">Add Dimension</h4>
        <button type="button" class="close" (click)="addModal.hide()" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form>
          <div class="form-group">
            <label for="formGroupInput">TAG</label>
            <input type="text" class="form-control" id="formGroupExampleInput" placeholder="Example input" aria-describedby="keyHelp"
              value="" disabled>
            <small id="keyHelp" class="form-text text-muted">Notice that this field is a surrogate key!</small>
          </div>
          <div class="form-group">
            <label for="formGroupInput2">Name</label>
            <input type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input" value="">
          </div>
          <div class="form-group">
            <label for="formGroupInput3">Description</label>
            <input type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input" value="">
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" id="dfsdfsdfsdf" class="btn btn-secondary" (click)="addModal.hide()">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

<!-- EDIT MODAL-->
<div bsModal #editModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title">Edit {{currentItem?.NAME}}</h4>
        <button type="button" class="close" (click)="editModal.hide()" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form>
          <div class="form-group">
            <label for="formGroupInput">TAG</label>
            <input type="text" class="form-control" id="formGroupExampleInput" placeholder="Example input" aria-describedby="keyHelp"
              value="{{currentItem?.TAG}}" disabled>
            <small id="keyHelp" class="form-text text-muted">You'll need to delete this entry to change this key value!</small>
          </div>
          <div class="form-group">
            <label for="formGroupInput2">Name</label>
            <input type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input" value="{{currentItem?.NAME}}">
          </div>
          <div class="form-group">
            <label for="formGroupInput3">Description</label>
            <input type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input" value="{{currentItem?.COMM}}">
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" (click)="editModal.hide()">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

import { Component, OnInit, ViewChild } from '@angular/core';
import { DataService } from "../data.service";
import { ModalDirective } from 'ngx-bootstrap/modal/modal.component';

@Component({

  templateUrl: './dimensions.component.html',
  styleUrls: ['./dimensions.component.scss']
})
export class DimensionsComponent implements OnInit {

  @ViewChild('editModal') public editModal: ModalDirective;
  @ViewChild('addModal') public addModal: ModalDirective;
  currentItem;

  openModal(item: any) {
    this.currentItem = item;
    this.editModal.show();

  }


  openAddModal() {
    this.addModal.show();

  }


  //Attributes
  public currentVar;
  subscription;
  dimensionData;
  rows;

  constructor(private _dataService: DataService) {


  }

Thank you for your time guys!

Update: By switching the modal sequence inside the code the problem also occures but just in terms of the other modal.

2 Answers 2

5

bsModal directive is not really suited for nested modals, please convert to usage of bsModal service, see example here: https://valor-software.com/ngx-bootstrap/#/modals#service-nested

Sign up to request clarification or add additional context in comments.

Comments

2

So after a few fixes, production use and testings I can say I have found a solution to a nested ngx-bootstrap modal problems. I have written a service-wrapper for a BsModalService.

modalIndexing = {};
hasInited = false;

constructor(
    router: Router,
    private modalService: BsModalService,
) {
    router.events
        .pipe(filter((event: NavigationEvent) => (event instanceof NavigationStart)))
        .subscribe(() => this.terminateAll());
}

// Just a modal dude
resolveModal(component, position: 'center' | 'static' = 'center', initialState?: any): void {
    const config: ModalOptions = {
        class: modalClassDict[position] + ' ss-modal',
        initialState
    };

    // For nested launches, wait until previous hide to open new
    setTimeout(() => {
        this.modalService.show(component, config);
    }, initialState.hasTimeout ? 600 : 0);
}

// Modal dude that will return something 
// if you want to of cause, no pressure
resolveModalSub(component, position: 'center' | 'static' = 'center', initialState?: any): Observable<any> {
    const config: ModalOptions = {
        class: modalClassDict[position] + ' ss-modal',
        initialState
    };

    const bsModalRef = this.modalService.show(component, config);
    bsModalRef.content.onClose = new Subject<any>();

    const modalSub = bsModalRef.content.onClose.pipe(take(1));
    this.healthCheck(modalSub);

    return modalSub;
}

// This sneaky little bastard will return something if it hides!
resolveModalSubCheckHide(component, position: 'center' | 'static' = 'center', initialState?: any): Observable<any> {
    const config: ModalOptions = {
        class: modalClassDict[position] + ' ss-modal',
        initialState
    };

    const bsModalRef = this.modalService.show(component, config);
    bsModalRef.content.onClose = new Subject<any>();

    const modalSub = bsModalRef.content.onClose.pipe(take(1));
    this.healthCheck(modalSub, true);

    return modalSub;
}

// This thingy does some pseudo-indexing and cleans up the mess you left
private healthCheck(modalSub, needToCheckHide = false) {

    // The only property ngx-bootstrap modal service
    // is able to return is the number of opened modals
    // so we are heading out from it 
    const index = this.modalService.getModalsCount();

    this.modalIndexing[index] = {
        modalSub,
        needToCheckHide
    };

    // First modal initiates onHide sub 
    if (!this.hasInited) {
        this.hasInited = true;

        this.modalService.onHide
            .subscribe(() => {
                if (_.keys(this.modalIndexing).length) {
                    // This event emits after the modal is closed so its a +1
                    const indexedSub = this.modalIndexing[this.modalService.getModalsCount() + 1];

                    // Handeles the case when you need to know if modal was closed
                    if (indexedSub.needToCheckHide) {
                        indexedSub.modalSub.next(false);
                    }

                    // Completes sub of a value return and cleans itself from dictionary
                    if (indexedSub.modalSub) {
                        indexedSub.modalSub.complete();
                    }
                    delete this.modalIndexing[this.modalService.getModalsCount() + 1];
                }
            });
    }
}

// Termiantes all modal windows in user navigates via back / forward
private terminateAll() {
    _.keys(this.modalIndexing).forEach(index => this.modalService.hide(index));
}

Basic usage:

const data = {
  // your initial data
};

this.dialogService.resolveModalSubCheckHide(YourModalComponentToDisplay, 'center', data)
  .subscribe((value) => {
      // processing
  });

Features:

  • Return value handling as subject
  • Performance friendly
  • Doesn`t leave any uncompleted subjects
  • Can return a value if hidden
  • Handles unlimited nested levels
  • Can be queued to launch modals
  • Auto close on manual navigation

Hope this helps somebody <3.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.