2

I have a collection of card objects

...
cards: Card[];
...
ngOnInit() {
  this.cardService.getCards.subscribe(r => { this.cards = r; });
}

I add child card components in the template like this

<div id="cards-container">
  <app-card *ngFor="let card of cards" [name]="card.name"></app-card>
</div>

The Card component has a name and some style dependant on an active attribute which is toggled by clicking the component

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  'styleUrls: ['./card.component.scss']
})
export class CardComponent {
  private _name = '';
  private _active = false;

  // getters/setters
  ...

  onClick(e) {
    this.active = !this.active;
  }
}

card.component.html

<div [ngClass]="{'card': true, 'active': _active}"
     (click)="onClick($event)">
  {{name}}
</div>

All of this works great.

The problem: In the parent component, I need to iterate through all of the card components which were added with *ngFor="let card of cards" and set them all active or not active but I cannot figure out how to do this.

What I have tried: I tried using @ViewChildren() but the QueryList's toArray() method always gives me an empty array. From the limited examples in the ViewChildren documentation I am not 100% clear whether I need an additional Directive in the parent component or if the directives in the examples are simply used to demonstrate. So what I tried was

@ViewChildren(CardComponent) cardList: QueryList<CardComponent>;

I also tried using ViewContainerRef using something similar to this answer but I was unable to make it work and it seems like that is not taking me in the right direction. I also looked at the documentation for ComponentRef but I do not see how or if this can help me solve my problem.

Any suggestions to point me in the right direction are appreciated.

UPDATE

My active setter in the card component is like this

@Input()
set active(active: boolean) {
  this._active = active;
}

I need to be able to change this for all the cards at any point, in other words, a "select/deselect all" option.

SOLVED!

Taking the suggestion from @Tim Klein, I subscribed to changes of the QueryList and was able to get my components in an array which I update when the QueryList changes. Now I just iterate the array of components and call my active setter.

cards: CardComponent[];
@ViewChildren(CardComponent) cardList: QueryList<CardComponent>;
...
ngAfterViewInit(): void {
  this.cards = this.cardList.toArray(); // empty array but that's okay
  this.cardList.changes.subscribe((r) => {
    this.cards = this.cardList.toArray(); // we get all the cards as soon as they're available
  });
}
Stack Underflow
  • 2,363
  • 2
  • 26
  • 51

1 Answers1

1

I think the problem you might be facing is that once the afterViewInit lifecycle event is called, your dynamic components still have not loaded. Therefore, if you are calling cardList.toArray() inside of afterViewInit, it will return an empty list because your components have not been added to the parent component's view yet.

You should try to subscribe to the changes observable (like in this example) and within the callback invoke your logic to alter the states of the children components.

Update

Another option is to simply supply another input for your child component that can accept a boolean for its active state.

Then, when you set the list of Card objects, simply iterate through and alter some active property. Simply set that property along with the name in the template for you parent component.

Tim Klein
  • 2,538
  • 15
  • 19
  • Thanks for your suggestions. I do have an `@Input()` decorator on the `active` setter in the card component but I need to be able to "select"/"deselect" all at any point in runtime, not just when they are first added. I will update my question with a little more information. – Stack Underflow Apr 15 '19 at 19:33