Search code examples
angulardynamicangular5angular-directive

angular5 dynamic compoments parent-chld communication


I'm implementing the approach described in the dynamic compoment docs.

I need my dynamic components to be able to emit an event to the parent component and in response the parent component might need to call a method of the child component.

I know how to bind child events to a parent's method when the child component is in the parent's template:

  • @Output() xevent : eventEmitter<string>; in child
  • <child-comp (xevent)="aParentMethod($event)" in parent template

However in the dynamic component approach, the parent template contains a directive, which in turn will wrap the dynamically instantiated component.

How can I set @Input and @Output properties on the dynamic component and propagate them from parent to child and vice-versa?

Moreover, how can I have the parent call a method on the child?


Solution

  • The angular docs are a little bit outdated as far as dynamic components go. Take a look at [ngComponentOutlet] directive introduced in Angular 4. It might simplify your component by a lot.

    The simple use case is as follows:

    import { Component } from '@angular/core';
    import { HelloComponent } from './hello.component';
    
    @Component({
      selector: 'my-app',
      template: `
        <ng-container [ngComponentOutlet]="component"></ng-container>
      `,
      styleUrls: [ './app.component.css' ]
    })
    export class AppComponent  {
      // [ngTemplateOutlet] binds to this property, you can set this dynamically!
      component = HelloComponent; 
    }
    

    More on NgComponentOutlet in the api documentation.

    So this is a good news. The bad news is that there is currently no way to access @Inputs and @Outputs of the component created this way. You can track this issue on github.

    In the mean time, some people suggested using ng-dynamic-component.

    You can also implement the parent/child communication using a shared service:

    app.component.ts

    import { Component } from '@angular/core';
    import {CommunicationService} from './communication.service';
    import {HelloComponent} from './hello.component';
    
    @Component({
      selector: 'my-app',
      template: `
      <input (keydown.enter)="send(input.value); input.value = ''" #input />
      <ng-container [ngComponentOutlet]="component"></ng-container>
      `,
      styleUrls: [ './app.component.css' ]
    })
    export class AppComponent  {
      component = HelloComponent;
    
      constructor(private communicationService: CommunicationService) {}
    
      send(val: string) {
        this.communicationService.next(val);
      }
    }
    

    communication.service.ts

    import {Injectable } from '@angular/core';
    import {Subject} from 'rxjs/Subject';
    import  {Observable } from 'rxjs/Observable';
    
    @Injectable()
    export class CommunicationService {
      private messageSource = new Subject();
      message$ = this.messageSource.asObservable();
    
      next(val: string) {
        this.messageSource.next(val);
      }
    }
    

    hello.component.ts

    import { Component, Input } from '@angular/core';
    import {Observable} from 'rxjs/Observable';
    import {CommunicationService} from './communication.service';
    
    @Component({
      selector: 'hello',
      template: `<h1>{{ message$ | async }} </h1>`,
      styles: [`h1 { font-family: Lato; }`]
    })
    export class HelloComponent  {
      message$: Observable<string>;
    
      constructor(private communication: CommunicationService) {
        this.message$ = communication.message$;
      }
    }
    

    Live demo