7

I have dynamically added a component on the click of a button .

Below is the code for my widget . Simple div with color property being set as input.

Widget.tmpl.html

 div class="{{color}}" (click)="ChangeColor()"

In the Widget component i am taking color as my input . This component works fine when i add it manually . But now i am trying to dynamically add the component and also need to pass the Color Value to the Widget Component.

Below is the code in the app.component.ts where i am calling addItem() on the button click .

app.component.ts

export class AppComponent  {
  @ViewChild('placeholder', {read: ViewContainerRef}) viewContainerRef;
  private componentFactory: ComponentFactory<any>;

  constructor(componentFactoryResolver: ComponentFactoryResolver, compiler: Compiler) {
    this.componentFactory = componentFactoryResolver.resolveComponentFactory(MyAppComponent);

  }

  addItem () {
   this.viewContainerRef.createComponent(this.componentFactory, 0);
  }

 public  myValue:string = 'red';

 onChange(val: any) { this.myValue = val; } }

In the addItem() method i am dynamically adding my widget component to my view. The component gets added fine . But the problem is how to pass the color property when dynamically adding . Based on what color i pass when creating the widget i want it to be displayed in red or green etc. How to property bind in this scenario?

Here is some of the Code :

export class MyAppComponent { 

    @Input() color; 
    @Output('changes') result: EventEmitter<any> = new EventEmitter(); 

    public constructor() { 
    }

    ChangeColor() {
        this.ToggleColor();
        this.result.emit(this.color);// Emitting the color to the parent.        
    }

    ToggleColor() {
        if (this.color == "red")
            this.color = "blue";
        else
            this.color = "red";
    }
}

In the above code i am emitting my color to the parent app.component.ts but since i have dynamically added the widget component , i dont know where to add this code (changes)="onChange($event)". I tried to add this code in the div as shown below :

<div class="{{color}}" (click)="ChangeColor()" (changes)="onChange($event)"></div>

But it does not work.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
maxrow
  • 71
  • 1
  • 1
  • 4

3 Answers3

14
var cmpRef = this.viewContainerRef.createComponent(this.componentFactory, 0);
cmpRef.instance.someProp = 'someValue';
cmpRef.instance.someObservable.subscribe(val => this.someProp = val);
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Also when i try to emit a event from my widget component using @Output . it is not firing . I have binded my emit event to the div of my widget :
    below is the code for widget.component.ts.
    – maxrow Aug 31 '16 at 08:53
  • Can you please edit your question and add the code there? Where is the HTML above? Is this in the view of the dynamically added component? Please add more code to make it obvious what is happening where. – Günter Zöchbauer Aug 31 '16 at 08:59
  • Please edit the question and add the code there. Code in comments is unreadable. – Günter Zöchbauer Aug 31 '16 at 09:19
  • `(someEvent)` doesn't listen on any event. The event has to be emitted by the `
    ` but it's emitted by `AppComponent`. Events emitted by `@Outputs()` also don't bubble.
    – Günter Zöchbauer Aug 31 '16 at 09:28
  • Thanks Gunter. So when we are dynamically generating the components @Outputs won't work is that correct. How to emit the event from div. – maxrow Aug 31 '16 at 09:49
  • Exactly. The last line in my code snipped in my answer is the workaround. `someObservable` can be an `EventEmitter` – Günter Zöchbauer Aug 31 '16 at 09:55
  • How do you fetch that instance data from an index element of this.viewContainerRef later on? ie this.viewContainerRef.get(0).instance... Inside each ViewRef of the ViewContainerRef this.viewContainerRef. object, I can see there is a nested private property called view: _view:_View_Node_Host0 Why is that property private and are there any plans in the future to offer a ViewRef accessor function to fetch that property and its nested values? As of now, I have had to store the cmpRef variable in a separate array in order to gain access to the instance data. – Benjamin McFerren Sep 01 '16 at 06:38
  • I'm not from the Angular2 team. You could create a feature request if you have a good use case to convince the Angular team to add it. Otherwise I would also use the array approach. – Günter Zöchbauer Sep 01 '16 at 06:41
  • 1
    that array is unneeded duplicity since the ViewContainerRef maintains order of viewRef's. The only value I see in the array approach is that it is a more accurate read on length since printing to the DOM takes more time than pushing onto an array. Just submitted https://github.com/angular/angular/issues/11223 Fingers crossed. Upvote it if you can. Thank you as always for your help Günter – Benjamin McFerren Sep 01 '16 at 06:58
  • This is an elegant answer. – Lucio Mollinedo Sep 22 '16 at 19:06
  • @GünterZöchbauer hi i have a problem with this kind of thing any way to contact you ? – LittleDragon Feb 24 '17 at 05:48
  • @LittleDragon please create a new question with the code that demonstrates what you try to accomplish, what you tried and where you failed. – Günter Zöchbauer Feb 24 '17 at 07:13
0

I will put this article if you can't find it in google https://netbasal.com/dynamically-creating-components-with-angular-a7346f4a982d It explains perfectly how to pass data, It helps me a lot to understand the resolveComponentFactory, Sorry for the answer is more like a comment but I do not have enough reputation.

Alejandro
  • 82
  • 3
  • 12
  • 1
    It's great that you linked an article, however, links don't last forever. Please paste the relevant parts fo the article into your post just in case the link dies. – Benjamin Urquhart Jul 19 '19 at 23:03
0

I have faced with the same issue: component processes input data and creates own appearance based on this data. The only one issue was adding component as regular one with html template (and predefined input data) and dynamically with componentFactory and setting data after ngInit angular event. It broke all initialization component logic due to triggering ngInit event before setting input data for component. One possible solution might be adding flexible component initialization based on allow initialization flag, that is set before adding this component and after settings input data.

 initComponentData() {
        const canInitData = !this.appService.deniedInitData.value;
        if (canInitData) {
            this.initData();
        } else {

            this.subscriptions.push(
                this.appService.deniedInitData.pipe(filter(x => !x),take(1)).subscribe(() => {
                    this.initData();
                }));
        }
    }

appService - Injectable angular service with

deniedInitData: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

Place when we are dynamically adding component:

this.appService.deniedInitData.next(true);
const cmpRef = this.viewContainerRef.createComponent(this.componentFactory, 0);

const instance = cmpRef.instance;
instance.someProp = 'someValue';                    
this.appService.deniedInitData.next(false);
Oleg Bondarenko
  • 1,694
  • 1
  • 16
  • 19