Handle @Input and @Output for dynamically created Component in Angular 2
Solution 1
You can easily bind it when you create the component:
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
ref.someData = { data: '123' }; // send data to input
ref.onClick.subscribe( // subscribe to event emitter
(event: any) => {
console.log('click');
}
)
ref.changeDetectorRef.detectChanges();
return ref;
}
Sending data is really straigthforward, just do ref.someData = data
where data
is the data you wish to send.
Getting data from output is also very easy, since it's an EventEmitter
you can simply subscribe to it and the clojure you pass in will execute whenever you emit()
a value from the component.
Solution 2
I found the following code to generate components on the fly from a string (angular2 generate component from just a string) and created a compileBoundHtml directive from it that passes along input data (doesn't handle outputs but I think the same strategy would apply so you could modify this):
@Directive({selector: '[compileBoundHtml]', exportAs: 'compileBoundHtmlDirective'})
export class CompileBoundHtmlDirective {
// input must be same as selector so it can be named as property on the DOM element it's on
@Input() compileBoundHtml: string;
@Input() inputs?: {[x: string]: any};
// keep reference to temp component (created below) so it can be garbage collected
protected cmpRef: ComponentRef<any>;
constructor( private vc: ViewContainerRef,
private compiler: Compiler,
private injector: Injector,
private m: NgModuleRef<any>) {
this.cmpRef = undefined;
}
/**
* Compile new temporary component using input string as template,
* and then insert adjacently into directive's viewContainerRef
*/
ngOnChanges() {
class TmpClass {
[x: string]: any;
}
// create component and module temps
const tmpCmp = Component({template: this.compileBoundHtml})(TmpClass);
// note: switch to using annotations here so coverage sees this function
@NgModule({imports: [/*your modules that have directives/components on them need to be passed here, potential for circular references unfortunately*/], declarations: [tmpCmp]})
class TmpModule {};
this.compiler.compileModuleAndAllComponentsAsync(TmpModule)
.then((factories) => {
// create and insert component (from the only compiled component factory) into the container view
const f = factories.componentFactories[0];
this.cmpRef = f.create(this.injector, [], null, this.m);
Object.assign(this.cmpRef.instance, this.inputs);
this.vc.insert(this.cmpRef.hostView);
});
}
/**
* Destroy temporary component when directive is destroyed
*/
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
The important modification is in the addition of:
Object.assign(this.cmpRef.instance, this.inputs);
Basically, it copies the values you want to be on the new component into the tmp component class so that they can be used in the generated components.
It would be used like:
<div [compileBoundHtml]="someContentThatHasComponentHtmlInIt" [inputs]="{anInput: anInputValue}"></div>
Hopefully this saves someone the massive amount of Googling I had to do.
thpnk
Updated on June 03, 2022Comments
-
thpnk almost 2 years
How to handle/provide
@Input
and@Output
properties for dynamically created Components in Angular 2?The idea is to dynamically create (in this case) the SubComponent when the createSub method is called. Forks fine, but how do I provide data for the
@Input
properties in the SubComponent. Also, how to handle/subscribe to the@Output
events the SubComponent provides?Example: (Both components are in the same NgModule)
AppComponent
@Component({ selector: 'app-root' }) export class AppComponent { someData: 'asdfasf' constructor(private resolver: ComponentFactoryResolver, private location: ViewContainerRef) { } createSub() { const factory = this.resolver.resolveComponentFactory(SubComponent); const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []); ref.changeDetectorRef.detectChanges(); return ref; } onClick() { // do something } }
SubComponent
@Component({ selector: 'app-sub' }) export class SubComponent { @Input('data') someData: string; @Output('onClick') onClick = new EventEmitter(); }