2

I am a newbie in angular2/4. I want to build shared modals with dynamic body and footer, then listen to which button of the footer that the user has pressed. I was inspired by the contribution of @echonax in plunker, I edited it as follows. I bound the button to eventEmitters

Template:

<div class="modal fade" id="theModal" tabindex="-1" role="dialog" aria-labelledby="theModalLabel">
  <div class="modal-dialog largeWidth" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title" id="theModalLabel">The Label</h4>
      </div>
      <div class="modal-body" #theBody>
      </div>
      <div class="modal-footer">
        <button class="btn btn-primary" (click)="submit()">save</button>
        <button class="btn btn-primary" (click)="edit()">edit</button>
        <button type="button" class="btn btn-default">Close</button>
      </div>
    </div>
  </div>
</div>

Component:

@Output() modalOutput: EventEmitter = new EventEmitter();

edit() {
  if (this.cmpRef) {
      this.cmpRef.destroy();
  }
  this.cmpRef = null;
  $('#theModal').modal('hide');
  this.modalOuput.emit('edit');
}  

submit() {
  if (this.cmpRef) {
     this.cmpRef.destroy();
  }
  this.cmpRef = null;
  $('#theModal').modal('hide');
  this.modalOuput.emit('submit');
}

and in the index.html:

<modal-comp (modalOutput)="modalData($event)"></modal-comp>

I used eventEmitter to catch the pressed button but I dont know how to listen to that event from the component which launched the modal.

AT82
  • 71,416
  • 24
  • 140
  • 167
blue
  • 525
  • 1
  • 8
  • 20
  • `@Output` emits the events to the parent component as you know. But `model-comp` does not have any parent components : / Instead you should use the shared service. Maybe you can create another `Subject` for the other events. – eko Jun 02 '17 at 10:25
  • yes it [worked](https://plnkr.co/edit/Qcry7naEFCvEr3oVwBYv?p=preview). But if we have multiple components that are subscribed to the same subject (onModalAction subject), they will all execute their callbacks once a button is clicked. How can we specify which component will handle that event without creating a subject for each component??? – blue Jun 02 '17 at 12:28
  • You usually solve this multi-component -> 1 output situations by moving your logic into a service (abstraction). Check this one: https://plnkr.co/edit/6RRpZ377C7ywQw3YVfSJ?p=preview. Rather than subscribing to the service in your component, let the service open the pop-up. You can give a callback function from your component to the service and when the close event is fired service will execute this callback. I might have explained it in a complex way :-) check the plunker and tell me your thoughts – eko Jun 02 '17 at 13:00
  • perfect!! thanks, I will post your proposition with a slight modifications – blue Jun 05 '17 at 10:07
  • Glad you figured it out :-) – eko Jun 05 '17 at 10:08

1 Answers1

1

We propose a solution that goes as follows: Make a shared service that handles the modal events (show, hide and callbacks when the modal buttons are clicked), then call that service from any component by passing custom callbacks (and why not a custom modal body). the modal component:

  export class ModalComponent {
  @ViewChild('theBody', {read: ViewContainerRef}) theBody;
  cmp:ComponentRef;
  data;

  constructor(
    private sharedService:SharedService, 
    private componentFactoryResolver: ComponentFactoryResolver, 
    injector: Injector) {

    sharedService.modalEvents.subscribe(data => {
      console.log("data", data);
      this.data = data.data;
      if(data.cmd == "show"){
        if(this.cmpRef) {
          this.cmpRef.destroy();
        }
        let factory = this.componentFactoryResolver.resolveComponentFactory(data.component);
        this.cmpRef = this.theBody.createComponent(factory)
        $('#theModal').modal('show');
      }else{
        this.dispose();
      }

    });
  }

   close() {
        //this.dispose();
        this.sharedService.hideModal(true, this.data);
    }

    dispose() {
        if (this.cmpRef) {
            this.cmpRef.destroy();
        }
        this.cmpRef = null;
        $('#theModal').modal('hide');
    }
  edit() {
    this.dispose();
     this.sharedService.modalEvents.next("edit");
  }

  submit() {
    this.dispose();
    this.sharedService.modalEvents.next("submit");
  }
}

and the shared service:

@Injectable()
export class SharedService {

  modalEvents: Subject = new Subject();

  popupData;
  hasModalShown;

  showModal(component:any, data?:any, callback?:any, cancelCallback?:any)  {
        //this.showModal.next(component);

        var innerFunc = ()=> {
            this.popupData = {cmd: "show", component: component, data: data,
            callback: callback, cancelCallback: cancelCallback };
            this.modalEvents.next(this.popupData);
            this.hasModalShown = true;
        }

        if (this.hasModalShown) {
            this.hideModal();
            //setTimeout(innerFunc,600);
        } else {
            innerFunc();
        }
    }

    hideModal(isSubmit:boolean=false, data?:any) {
        if (this.hasModalShown) {
            this.modalEvents.next({cmd: "hide"});
            if(isSubmit && this.popupData.callback){
              this.popupData.callback(data);
            }else if(this.popupData.cancelCallback){
              this.popupData.cancelCallback(data);

            }

            this.hasModalShown = false;
        }
    }
}

this is a working example in plunker

blue
  • 525
  • 1
  • 8
  • 20