Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modal): add functionality for identifying which modal triggered the events #2917

Closed
Varshamathukumalli opened this issue Oct 26, 2017 · 10 comments · Fixed by #4254
Closed

Comments

@Varshamathukumalli
Copy link

Varshamathukumalli commented Oct 26, 2017

Suppose we have two modals in a component and are subscribed to events available, we are unable to identify from which modal is triggered.

Plunkr: https://embed.plnkr.co/jas1u9F7DchyBeN33hCw/

@IlyaSurmay
Copy link
Contributor

IlyaSurmay commented Oct 27, 2017

You're right, there's no functionality that can help a user to identify which modal has triggered the event. We need to implement an id of modal or something like that. Btw, a small PR with this fix would be helpful :)

@psined1
Copy link

psined1 commented Nov 9, 2017

Any idea when this might be implemented?

@YevheniiaMazur YevheniiaMazur changed the title Unable to identify which modal triggred the events when multiple modals are available feat(modal): add functionality for identifying which modal triggered the events Jan 15, 2018
@Varshamathukumalli
Copy link
Author

When can this be expected to be released

@psined1
Copy link

psined1 commented Jan 31, 2018

Here's my solution. This works well with templated dialogs. It might not work for everyone, but I thought it'd be good to share anyway. Create a separate service. In my example it is "SessionService". Add two public members to it, one being a semaphore counting the number of open open modals (this can be useful to see if stacked dialogs are still open), another an eventEmitter with string payload.

@Injectable()
export class SessionService implements OnDestroy {
    private _modals = 0;
    private subscriptions: Subscription[] = [];
    public childUpdated = new EventEmitter<string>();
    public get isModal(): boolean {
        return this._modals > 0;
    }
    constructor(private modalService: BsModalService
    ) {
        this.subscriptions.push(this.modalService.onShow.subscribe((evt: any) => this._modals++));
        this.subscriptions.push(this.modalService.onHide.subscribe((evt: any) => this._modals--));
    }
    ngOnDestroy() {
        this.subscriptions.forEach(s => s.unsubscribe());
    }
}
@Component({...})
export class DialogComponent {
   @Input() dialogId: string = 'some default id';
   public modalRef: BsModalRef = null;
   ....
   constructor(private sessionService: SessionService) { }
   hideMe() {
      if (this.okPressedOrSomethingLikeThat) {
        this.sessionService.childUpdated.emit(this.dialogId);
      }
      if (this.modalRef) this.modalRef.hide();
    }
    public initModal(modalRef: BsModalRef, dialogId: string) {
      this.modalRef = modalRef;
      this.dialogId = dialogId;
      ...
    }
}
@Component({...})
export class HostComponent {
    ....
    constructor(private sessionService: SessionService) { }
    ngOnInit() {
        ....
        this.subscriptions.push(this.sessionService.childUpdated.subscribe((child: string) => {
            switch (child) {
                case 'dialogId1':
                case 'dialogId2':
                    break;
            }
        }));
    }
    ngOnDestroy() {
        this.subscriptions.forEach(s => s.unsubscribe());
    }
    public showDialog1(): void {
        const modalRef = this.modalService.show(DialogComponent,
            Object.assign({}, {
                animated: true,
                keyboard: true,
                backdrop: true,
                ignoreBackdropClick: false
            }, { class: 'modal-lg' })
        );
        (<DialogComponent>modalRef.content).initModal(modalRef, 'dialogId1');
    }
}

You can apply the same technique to nested dialogs as well. The hosting parent will get events with proper dialog ids.

// D

P.S. sorry for the garbled up code format - not my fault ;)

@jamesmikesell
Copy link

So I'm not supper content with this solution, however here is workaround i have for this issues:

let thisModalNumber = this.modalService.getModalsCount();
this.modalService.show(MyDialogComponent);

let sub = this.modalService.onHide.subscribe(() => {
    if (thisModalNumber === this.modalService.getModalsCount()) {
        if (sub)
            sub.unsubscribe();

        //
        // rest of your code here to process after only this dialog was closed (not a nested child)
        //
    }
});

@instantaphex
Copy link

Why not have the modal service show method return an observable? This would make things a lot less complicated it seems. The modalRef hide method could take an argument that the observable could emit.

@Component({
    selector: 'my-modal',
    template: `
<div class="modal-footer">
  <button type="button" class="btn btn-primary" (click)="bsModalRef.hide(true)">Ok</button>
  <button type="button" class="btn btn-default" (click)="bsModalRef.hide(false)">Cancel</button>
</div>
`
})
export class ConfirmationComponent {}
this.modalService.show(ConfirmationComponent)
    .take(1)
    .subscribe((resp) => {
        console.log(`user clicked ${resp ? 'ok' : 'cancel'}`);
    });

The onShow and onHide events really have no business being on the service. The events should come from the modalRef itself. I haven't looked at the source so I don't know if this change would be too big of an undertaking given the current code base but I think it would probably simplify things, not only for the user-facing API, but for the code base itself.

@instantaphex
Copy link

I've come up with a solution that works for opening modals using the service. This approach could probably be used just as easily for template modals as well. Basically I created a simple base modal component:

import {BsModalRef} from 'ngx-bootstrap';
import {Subject} from 'rxjs/Subject';

export class BaseModalComponent {
  public onHide: Subject<any> = new Subject<any>();

  constructor(private bsModalRef: BsModalRef) {}

  close(data: any = null): void {
    this.onHide.next(data);
    this.bsModalRef.hide();
  }
}

All of the modal components that I create extend this class and pass the modal ref into the super call:

import {Component} from '@angular/core';
import {BsModalRef} from 'ngx-bootstrap';
import {BaseModalComponent} from '../base-modal/base-modal.component';

@Component({
  selector: 'app-confirmation-modal',
  templateUrl: `
<div class="modal-header">
  <h4 class="modal-title pull-left">{{titleMsg}}</h4>
  <button type="button" class="close pull-right" aria-label="Close" (click)="close(false)">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
<div class="modal-body">
  <span>{{confirmationMsg}}</span>
</div>
<div class="modal-footer">
  <button type="button" class="btn btn-primary" (click)="close(true)">Ok</button>
  <button type="button" class="btn btn-default" (click)="close(true)">Cancel</button>
</div>
`,
  styleUrls: ['./confirmation-modal.component.scss']
})
export class ConfirmationModalComponent extends BaseModalComponent {
  confirmationMsg = '';
  titleMsg = '';
  constructor(bsModalRef: BsModalRef) {
    super(bsModalRef);
  }
}

then the caller is able to do this:

this.modalService.show(ConfirmationModalComponent).content.onHide
    .take(1)
    .subscribe((result: boolean) => {
        console.log(`user clicked ${result ? 'ok' : 'cancel'}`);
    });

Using this approach, you never have to listen to this.modalService.onHide, which will always fire no matter which modal is hidden. In my case I have multiple modals opened with the same modal service and closing any of them fired event handlers for all of them.

@dkosasih
Copy link
Contributor

@instantaphex
That's what exactly I am doing at the moment, minus the super class.
There was a scenario that makes me create a pull request.
I cannot remember why I did that.

meshubh added a commit to meshubh/ngx-bootstrap that referenced this issue Jun 18, 2019
…the events

emitting the dialogRef on modals onShown and onHidden events.

This closes valor-software#2917
@px751
Copy link

px751 commented Aug 7, 2019

Hello guys, any news on this issue? :)

@osiala
Copy link

osiala commented Aug 21, 2019

When we use modal with component, the associated component will be destroyed when you close/hide your popup so we can identify close/hide event using the ondestroy event of the associated component :

in your parent component where you initialize the modal you define your modal using observable like that :
` let destroySubject = new Subject();

        new Observable<Callback>(observer => {
            const initialState = {
                onConfirm: (callback: Callback) => {
                    observer.next(callback);
                },
                destroySubject
            };
            this.modalService.show(CallbackModalComponent, {initialState, class: 'modal-lg'});
        })
      .pipe(takeUntil(destroySubject))
      .subscribe();`

the destroySubject is the subject related to the modal component destroy event.

The modal component take in params the initialState and the destroySubject that will be linked with the onDestroy like that :

`export class CallbackModalComponent implements OnInit, OnDestroy {

callback: Callback;
onConfirm: (callback: Callback) => void;
destroySubject: Subject<void>;

constructor(public bsModalRef: BsModalRef) {
}

ngOnDestroy(): void {
    this.destroySubject.next();
}

ngOnInit() {
}

onSubmit() {
    this.onConfirm(callback);
    this.bsModalRef.hide();
}

}`

the observable that initializing you modal will complete when the modal is hided.
So here each modal will have its onHide event based on the onDestroy of the related modal component.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment