18

I have a QueryList in a component. I am adding dynamically other components, that will be present in the QueryList, but if I subscribe to the changes of the QueryList, nothing happens. I thought it is because I subscribed in ngAfterViewInit, but the QueryList is undefined yet in ngOnInit.

Here I have the plunkr.

code:

@Component({
  ...
})
export class AppComponent {

    @ViewChildren(ControlFactoryDirective) factories:
        QueryList<ControlFactoryDirective>
    
    ngAfterViewInit() {
      this.factories.changes.subscribe(() => {
        console.log('changed')
      })
    }
}
Roman C
  • 49,761
  • 33
  • 66
  • 176
István
  • 5,057
  • 10
  • 38
  • 67

4 Answers4

23

The thing is, the list that comes to your factories property is already pre-populated. So you just don't have a chance to subscribe to its changes before it's populated for the first time. But all changes that happen later are coming through to your subscriber, of course.

Here is the plunkr with your example updated - notice that factories now has a setter which proves that it's prepopulated (you normally don't need it in a real app, that's for finding out only what value comes from the framework)

I think it's a totally valid behavior. So in ngAfterViewInit, loop over values in the QueryList and do the same thing you are about to do in the subscriber:

ngAfterViewInit() {
  this.processChildren(); // first call to processChildren

  this.factories.changes.subscribe(_ => 
      this.processChildren() // subsequent calls to processChildren
  );
}

private processChildren(): void {
  console.log('Processing children. Their count:', this.factories.toArray().length)
}
Mikha
  • 491
  • 2
  • 13
  • Note that `changes` will no longer be issued if you override the `notifyOnChanges` method. Unless you manually issue a value inside the overriden `notifyOnChanges` method I guess (I did not test that assumption) – M'sieur Toph' Apr 08 '20 at 07:10
6

You can also add startWith(undefined) like this:

ngAfterViewInit() {
  this.factories.changes.pipe(startWith(undefined)).subscribe(() => 
  {
     console.log('changed')
  })
}

This will trigger the subscription handler immediately.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • 1
    `startWith(undefined)` is deprecated now, you should use `startWith([undefined])`. – yktoo Sep 27 '21 at 17:07
  • @yktoo oh no I'm going to have to fix it in a lot of places. I wish Angular would just give me the value + changes in a single observable. Applies to FormGroup.changes too. – Simon_Weaver Sep 27 '21 at 20:23
  • 1
    `startWith([undefined])` saved my day after two hours of searching. – Felipe Morais Oct 27 '22 at 01:17
3

After you make a change to the objects in query list you should call query list to notify on changes.

update() {
  this.factories.forEach(factory => {
    console.log(factory.componentRef.instance.model.data = "alma")
  //  factory.()
  this.factories.notifyOnChanges();
  })
}
Roman C
  • 49,761
  • 33
  • 66
  • 176
  • Then I have to add the `notifyOnChanges` in `ControlFactoryDirective`'s `create`, but I cannot add there ... I want to detect the changes when adding the components – István Apr 03 '17 at 12:12
1
export class AppComponent {
    factoryList : any = [{ id: 1,name: "abc", detail: "abc abc abc"}];

    @ViewChildren(ControlFactoryDirective) factoryDirective:
        QueryList<ControlFactoryDirective>
    
    ngAfterViewInit() {
      this.factoryDirective.changes.subscribe(() => {
        console.log('changed')
      })
    }
    addNewfactory(){
     this.factoryList .push({ id: 11,name: "xyz", detail: "xyz xyz xyz"});
    }
}

I assume that ControlFactoryDirective is used in html like below. Number of the components and used Factory objects depends on the factoryList size. When button is clicked a new Factory object will be added to the factoryList. factoryList size is changed, new ControlFactoryDirective component is added/displayed, also subscribe method will be called. 

<button (click)="addNewfactory()">Add</button>

<controlfactorydirective *ngFor='let factory of factoryList'
  [factory]="factory">
</controlfactorydirective >
nesnes
  • 171
  • 1
  • 2