5

I have a simple demo app which I'm simulating manually insert / fetch data from DB and injecting new components - according to the num entered.

Plunker

enter image description here

So if I click the "manual " button twice :

enter image description here

And if I set "3" in the text and click "fetch from db" - I get the expected delay(simulate db) and then :

enter image description here

This all works as expected.

The "parent" component is :

//src/MainPage.ts
@Component({
  selector: 'my-app',
  template: `
    <button (click)="putInMyHtml()">Insert component manually</button>
    <p> # Items to fetch  : <input type="text" style='width:40px'  [(ngModel)]="dbNumItems" name="dbNumItems"/> <input type='button' value='fetch from db' (click)='fetchItems($event)'/></p>
    <div #myDiv>
       <template #target></template> 
    </div>
  `
})
export class MainPage {
  @ViewChild('target', { read: ViewContainerRef }) target: ViewContainerRef;
  dbNumItems: string;
  constructor(private cfr: ComponentFactoryResolver) {}

  fetchItems(){ 
        var p= new Promise((resolve, reject) => { //simulate db
        setTimeout(()=>resolve(this.dbNumItems),2000)
    });

  p.then(v=>{

    for (let i =0;i<v;i++)
    {
         this.putInMyHtml() ;// inject "v" times
    }
  })

}

  putInMyHtml() {
   // this.target.clear();
   let compFactory = this.cfr.resolveComponentFactory(TestPage);
    this.target.createComponent(compFactory);
  }
}

This is the Injected component :

//src/TestPage.ts
@Component({
  selector: 'test-component',
  template: '<b>Content  : Name={{user.Name}} Age={{user.Age}}</b><br/>',
})
export class TestPage {
    @Input
     User:Person;

}

So where is the problem ?

As you can see , in the injected component I have :

@Input
User:Person;

which means that I want the parent component to pass a Person object to each injection.

In other words :

Question

Looking at the "after db stage" , How can I pass a customized person to each injection ?

  p.then(v=>{

    for (let i =0;i<v;i++)
    {
         let p = new Person();
         p.Name = "name"+i;
         p.Age = i;

         this.putInMyHtml() ; //how to I pass `p` ???

    }
  })

}

Expected output :

enter image description here

NB I don't want to use ngFor because I don't need to hold an Array at the back end. this is an app which injects new articles periodically.and I will be glad to know if there's a better way of doing it.

eko
  • 39,722
  • 10
  • 72
  • 98
Royi Namir
  • 144,742
  • 138
  • 468
  • 792

2 Answers2

6

You can do it with the instance property of component ref like this:

putInMyHtml(p) {
   // this.target.clear();

    let compFactory = this.cfr.resolveComponentFactory(TestPage);

    let ref = this.target.createComponent(compFactory);
    ref.instance.user = p;
}

-Fixed the @Input() binding, syntax was wrong.

-Added a safe-navigation operator (?) for the template to do the null checks for the async input.

Fixed plunker: https://plnkr.co/edit/WgWFZQLxt9RFoZLR46HH?p=preview

eko
  • 39,722
  • 10
  • 72
  • 98
  • Exactly what I wanted. Thank you. BTW is my way of thinking right when I say that I don't want ngFor because I don't need/want to hold an Array at the back end (memory stuff etc) - I just want to inject new articles . Is it the right approach ? – Royi Namir Apr 01 '17 at 18:59
  • @Royi well the "right" approach depends on your architecture of course and I wouldn't want to give you wrong advice :-) However, I didn't understand why you need a component for this. As you said `*ngFor` can do this with an array of objects (new articles). As you periodically update the input that `*ngFor` holds, you can pretty much do the same thing. – eko Apr 01 '17 at 19:03
  • Because I don't need the array at all. and I don't want that each "append" to the array - to "render" all items from the beginning. ( if the injected control will have an IMG , I don't want the SRC to tun each time a new article arrives. – Royi Namir Apr 01 '17 at 19:05
  • @Royi you don't need to push the new content to the array, you can replace the array by assigning the new array of objects to the existing array, therefore the old ones won't show. – eko Apr 01 '17 at 19:09
  • What do you mean " therefore the old ones won't show" ? I want them to be there. it's like infinite scroll where a new items arrives , I append them. The odl ones are still/should be there ( without re-render)I'll be happy to see an example – Royi Namir Apr 01 '17 at 19:10
  • @Royi ohh I misunderstood that sorry – eko Apr 01 '17 at 19:10
  • @Royi last suggestion, would the slice pipe be a better alternative for you? https://angular.io/docs/ts/latest/api/common/index/SlicePipe-pipe.html you still push to the array but `*ngFor` only shows the values from the last length to new length. – eko Apr 01 '17 at 19:13
  • Mmmm on the contrary - I don't need any "filtering" or "reducing" a collection. I need append and always append. Also , when an another user upload an article ( via signalR) , the server broadcasts to all clients " here is a new article" , and all clients should append that article ( while keeping the already fetched from prev fetches). This is - why - as you can see - I don't need an array at the back end. – Royi Namir Apr 01 '17 at 19:16
  • @Royi Ah ok then :-) – eko Apr 01 '17 at 19:17
  • Good one, quite precise. I believe there already are answers that explain input bindings for dynamically compiled components, but they are either quite hard to follow or written for obsolete A2 versions. – Estus Flask Apr 01 '17 at 20:42
1

use *ngFor and iterate through an array of Person, that way you can use the @Input. You probably want something like

<ul>
  <li *ngFor="let person of people">
    <test-component [User]=person></test-component>
  </li>
</ul>

add people: Person[] to your main component and when you fetch items

p.then(v=>{

for (let i =0;i<v;i++)
{
   let p = new Person();
   p.Name = "name"+i;
   p.Age = i;  
   people.push(p)
}
})
Phil DiMarco
  • 51
  • 2
  • 7
  • But if the injected control will have an IMG , I don't want the "already exists" SRCs to run each time a new article arrives.For example say I have already array[5] with articles and I push another 2 , I don't want the 5 articles to be render again – Royi Namir Apr 01 '17 at 19:07
  • Create a service that you can add new articles to. In the main component subscribe to that service and when a new article comes in just append it to the array. – Phil DiMarco Apr 01 '17 at 19:19
  • Will old articles that's already in the array - will be re-render if I add one ? – Royi Namir Apr 01 '17 at 19:20
  • I am almost certain that the whole list does not get re-rendered (if it does it happens too fast for me to notice). But either way since it is already in an array you won't have to remake the requests to the server. – Phil DiMarco Apr 01 '17 at 19:28
  • It won't get re-rendered when a new value is pushed to the array. Check this: http://stackoverflow.com/a/42244702/5706293 – eko Apr 01 '17 at 19:32