13

I'm trying to send data to a custom modal content component so I can call it from any other component and not repeat code. I'm new to Angular 2 and have followed the "Components as content" demo of ng-boostrap as well as the "Component Interaction" in the Angular docs and have not found a way to get this to work or an example for this case.

I can get the modal to open, but not with dynamic content. I've tried the @Input and the variable approach with no success. I've also added ModalService to the providers in app.module.ts. This is what I have with both approaches that doesn't work:

page.component.html:

<a (click)="modal('message')">
<template ngbModalContainer><my-modal [data]="data"></my-modal></template>

page.component.ts:

import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ModalService } from '../helpers/modal.service'
import { ModalComponent } from '../helpers/modal.component'

@Component({
  selector: 'app-page',
  templateUrl: './page.component.html',
  styleUrls: ['./page.component.scss'],
  entryComponents: [ ModalComponent ]
})

export class PageComponent {
  data = 'customData'
  constructor (
    private ngbModalService: NgbModal,
    private modalService: ModalService
   ) { }

  closeResult: string
  modal(content) {
    this.data = 'changedData'
    this.modalService.newModal(content)
    this.ngbModalService.open(ModalComponent).result.then((result) => {
      this.closeResult = `Closed with: ${result}`
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`
    });
  }
}

modal.service.ts:

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class ModalService {
  private modalSource = new Subject<string>()
  modal$ = this.modalSource.asObservable()
  newModal(content: string) {
    this.modalSource.next(content)
  }
}

modal.component.ts:

import { Component, Input, OnDestroy } from '@angular/core';
import { Subscription }   from 'rxjs/Subscription';
import { ModalService } from './modal.service'

@Component({
  selector: 'my-modal',
  template: `
    <div class="modal-body">
    {{data}}
    {{content}}
    </div>
  `
})

export class ModalComponent implements OnDestroy {
  @Input() data: string
  content = 'hello'

  subscription: Subscription
  constructor(
    private modalService: ModalService
  ) {
    this.subscription = modalService.modal$.subscribe(
      content => {
        this.content = content
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Using angular v2.1.0, angular-cli v1.0.0-beta.16, ng-bootstrap v1.0.0-alpha.8

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
asabido
  • 133
  • 1
  • 1
  • 6

3 Answers3

14

I solved it! Here's how to do it:

  • In the component(from where you are opening the modal) do this:

    const modalRef = this.modalService.open(ModalComponent);
    
    modalRef.componentInstance.passedData= this.dataToPass;
    
    modalRef.result.then(result => {
      //do something with result
    }                                                       
    
  • In the modal component(receiving end):

    export class ModalComponent { 
    
     passedData: typeOfData;     // declare it!
    
     someFunction() {     // or some  lifecycle hook 
      console.log(this.passedData)  // and then, use it!
     }
    
    // rest of the ModalComponent 
    }
    
BlackBeard
  • 10,246
  • 7
  • 52
  • 62
  • make sure you aren't declaring the incoming data with prefix `@Input` – Elysiumplain Mar 25 '19 at 19:09
  • @Elysiumplain can you explain more (reasoning)? – Maayao Jun 18 '19 at 13:07
  • 1
    @Maayao It has been a long time, but IIRC it has to do with the was Angular's internals treat variable handling; with @Input() and Services like modalService declaration not sharing the same namespace. *note that this may have changed with newer version – Elysiumplain Jul 08 '19 at 23:55
3

Just provide a service and inject it to VideoModalComponent and PageComponent then you can use this service to communicate.

See https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service for more details and examples.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks Günter, I went back and implemented the service (see edited entry) but it's still not working. Perhaps the ngbModalService is creating a new instance of my modal component so they're not communicating. Any ideas seeing the update? – asabido Oct 15 '16 at 17:16
  • Either provide the service in the root component or make it a singleton service. It should work. – ZooZ Oct 16 '16 at 09:10
  • 1
    Where do you provide `ModalService`? How many instances you get depends on where you provide it. If you add it to `providers` of a component, each such component will get it's own instance. If you add it to `providers` of `@NgModule()` (non-lazy loaded) then only one instance is created for the whole app. – Günter Zöchbauer Oct 16 '16 at 15:28
  • @GünterZöchbauer I've tried including `ModalService` as a provider in `@ngModule()` and then tried in `PageComponent` and neither work. – asabido Oct 16 '16 at 19:07
  • Can you try to reproduce in Plunker (they have a working Angular2 TS template) – Günter Zöchbauer Oct 16 '16 at 19:15
  • 2
    The problem seems to be that the observable emits the value before the subscription is set up. If you use a `BehaviorSubject` instead, then the subscriber gets the last emitted value immediately after subscribing. https://plnkr.co/edit/y28ppeYk7ZQ4nsOFmIi1?p=preview – Günter Zöchbauer Oct 17 '16 at 14:27
0

Here are some ways to do it.

  1. Expose an instance of a component
  2. Use resolve and expose resolved values on an instance of NgbActiveModal
  3. Use resolve and bind resolved values to component's inputs.

See: https://github.com/ng-bootstrap/ng-bootstrap/issues/861#issuecomment-253500089

Joel Raju
  • 1,210
  • 2
  • 18
  • 21