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 templateHowever 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?
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$;
}
}