83

Is there a method that can be used to define an @Input property on an Angular 2 component that's created dynamically?

I'm using the ComponentFactoryResolver to create components in a container component. For example:

let componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentName);

let componentRef = entryPoint.createComponent(componentFactory);

Where "entryPoint" is something like this in the component HTML:

<div #entryPoint></div>

And defined in my container component with:

@ViewChild('entryPoint', { read: ViewContainerRef } entryPoint: ViewContainerRef;

This works well, but I can't find a way to make an @Input property work on the newly created component. I know that you can explicitly set public properties on the component class, but this doesn't seem to work with ng-reflect. Prior to making this change I had a "selected" property decorated with "@Input()" that caused Angular to add the following to the DOM:

<my-component ng-reflected-selected="true"></my-component>

With this in place I was able to dynamically update the markup to switch a CSS class:

<div class="header" [class.active-header]="selected === true"></div>

Based on some searching I was able to find a method to make "@Output" work as expected, but I've yet to find anything for @Input.

Let me know if additional context would be helpful and I'd be happy to add it.

badger2013
  • 1,123
  • 2
  • 8
  • 10

3 Answers3

84

No, Angular2 bindings only work with components and directives statically added to the template of a component.

For all other situations use shared services like explained in https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

You can also use

let componentRef = entryPoint.createComponent(componentFactory);
componentRef.instance.someProp = 'someValue';
componentRef.instance.someObservableOrEventEmitter.subscribe(data => this.prop = data);
FIL
  • 1,148
  • 3
  • 15
  • 27
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • How would you change the .style property of the component's resulting DOM element? ie this.cmpRef._hostElement.nativeElement.style... without using the private variable – Benjamin McFerren Sep 09 '16 at 16:05
  • 1
    You can add `@HostBinding('style.border') borderStyle:string;` to the component and then set it like `componentRef.instance.borderStyle = 'solid 3px red';` Not sure if this is what you want. Otherwise there aren't many options to set styles dynamically. – Günter Zöchbauer Sep 09 '16 at 16:12
  • @BenjaminMcFerren did you downvote? Was this accidentally? – Günter Zöchbauer Sep 09 '16 at 16:12
  • I did not intentially downvote. Apologies for this - I think I was just able to amend this accident by upvoting back to zero. But I see a stackoverflow ui bug that won't allow me to now upvote so that it becomes 1 – Benjamin McFerren Sep 09 '16 at 17:07
  • thank you for your help. If instead, I just passed an object property to the child (that contains key/prop for each style I want to change), what syntax would I use in the child to change its resulting dom element? – Benjamin McFerren Sep 09 '16 at 17:10
  • I don't know a way to style the child element itself this way. You could add `[ngStyle]="someStyle"` to a child element of the dynamically added element and style this by setting `someStyle` to the object you mentioned. You could now try to upvote again. I might work now. – Günter Zöchbauer Sep 09 '16 at 17:23
  • No worries. I was just wondering if someone thinks there is something wrong with my answer. – Günter Zöchbauer Sep 09 '16 at 17:43
  • never me - I am extremely grateful for the help you have shared with me – Benjamin McFerren Sep 10 '16 at 01:35
  • @GünterZöchbauer I'm interested in using the imperative approach you've outlined here but whenever I try it I get type errors that the EventEmitter property does not exist on type {}. Is there any workaround for this? – Jesse Carter Jan 12 '17 at 21:26
  • The component needs to have an `someObservableOrEventEmitter:EventEmitter = new EventEmitter()`. If this doesn't help, please create a new question with the code you are using and eventually a Plunker that allows to reproduce. – Günter Zöchbauer Jan 12 '17 at 21:27
  • @GünterZöchbauer All of the components that I've included in entryComponents contain the EventEmitter property. I think that the problem I'm struggling with is how how the type system actually knows what the type of ComponentRef will ultimately be to be aware of the Emitter – Jesse Carter Jan 12 '17 at 21:34
  • 1
    I managed to get it to work by casting the ComponetRef to "any": let componentRef = this.dynamicComponentContainer.createComponent(factory); let dynamicRef = componentRef as any; dynamicRef.instance.change.subscribe(x => console.log(x)); Not sure if this is a good idea though because it breaks type safety for the dynamically created components – Jesse Carter Jan 12 '17 at 21:48
  • 1
    I think `CompinentRef` should work as well – Günter Zöchbauer Jan 13 '17 at 04:12
  • 4
    When I do this, componentRef.instance.someProp = 'someValue'; doesn't trigger ngOnChanges on the child component. Are there more steps that need to be done for this tow work? – Jus10 Mar 30 '17 at 21:58
  • I posted a comment to http://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-2-http-network-call-in/36291681#36291681 – Günter Zöchbauer Mar 31 '17 at 05:14
  • @Jus10 You can see my answer if you're still curious... five years later. – hovado Oct 07 '22 at 06:49
18

Since 14.1.0, Angular added the setInput() method to directly set or update @Input to the component reference.

this.componentRef.setInput('someInput', this.value);

Using this method will properly mark for check component using the OnPush change detection strategy. It will also assure that the OnChanges lifecycle hook runs when a dynamically created component is change-detected.

If the value will change, I use this approach:

@Input()
public value: string;    

ngOnInit(): void {
    this.viewContainerRef.clear();
    this.componentRef = this.viewContainerRef.createComponent(this.component);
}

ngOnChanges(changes: SimpleChanges): void {
    if (changes.value) {
        this.componentRef?.setInput('someInput', this.value); // Updated with every change from the parent component.
    }
}

ngOnDestroy(): void {
    this.componentRef?.destroy(); // Don't forget to destroy the component.
}

API

hovado
  • 4,474
  • 1
  • 24
  • 30
2

The Günter Zöchbauer's answer is correct. But if you, like I was, have troubles with rendering @Input data in the component template, you should try this:

  1. Run your component creating code in ngAfterViewInit.
  2. Add componentRef.instance.ngOnInit() as a last line.
  3. Inside ngOnInit of your dynamically created component run detectChanges method of the ChangeDetectorRef.
Eugene P.
  • 1,517
  • 12
  • 16