How to update the styles of @ViewChildren (Or @ContentChildren) in Angular(v14)

1.3k views Asked by At

I'm trying to create a carousel component that has n amount of child components based on whatever size list it will get. I want the parent (carousel) to control the styling for children as it manipulates the size of the list and track the indexes to rotate items around the carousel and style accordingly. There will buttons to control this behaviour. example of the carousel

I'm trying to apply styles and get the functionality working but cannot seem to access the styles by getting a query list of element refs using view children and template refs (I also tried with content children initially, with the template set up properly). I can change them if I inject the element ref and renderer at the child level, but I don't want to do this here. I get the error: TypeError: Cannot read properties of undefined (reading 'style') But not when I do the exact same thing doing it from the child level.

Am I going about this incorrectly? How do I change children styles in a list from @ViewChildren() from a parent?

Parent component (carousel.component.html)

  <div class="caro-body">
    <h3 class="caro-body__title">Carousel Title</h3>
    <div class="caro-body__items">
      <app-upcoming-card
      #caroItem
      *ngFor="let card of list; let i = index"
      ></app-upcoming-card>
    </div>
  </div>

(carousel.component.ts)

import {
  Component,
  OnInit,
  AfterViewInit,
  Renderer2,
  ViewChildren,
  QueryList,
  ElementRef,
} from '@angular/core';
import { UpcomingCardComponent } from '../upcoming-card/upcoming-card.component';

@Component({
  selector: 'app-carousel',
  templateUrl: './carousel.component.html',
  styleUrls: ['./carousel.component.scss'],
})
export class CarouselComponent implements OnInit, AfterViewInit {
  @ViewChildren('caroItem') caroItems: QueryList<ElementRef<UpcomingCardComponent>>

  list = [
    { billName: 'Rent', billAmount: 850, billDate: Date.now() },
    { billName: 'Groceries', billAmount: 250, billDate: Date.now() },
    { billName: 'Internet', billAmount: 80, billDate: Date.now() },
    { billName: 'Phone', billAmount: 45, billDate: Date.now() },
    { billName: 'Loan', billAmount: 50, billDate: Date.now() },
    { billName: 'Transit', billAmount: 20, billDate: Date.now() },
    { billName: 'Dining Out', billAmount: 50, billDate: Date.now() },
  ];

  constructor(private renderer: Renderer2) {}

  ngOnInit(): void {}

  ngAfterViewInit() {
    this.caroItems.forEach(item => {
      this.renderer.setStyle(item.nativeElement, 'background-color', 'red')
    })
  }

}

Child component (upcoming-card.component.html)

<div class="card">
  <div class="card__title">{{ card.billName }}</div>
  <div class="card__amount">{{ card.billAmount | currency }}</div>
  <div class="card__date">{{ card.billDate | date }}</div>
</div>

(upcoming-card.component.ts)

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-upcoming-card',
  templateUrl: './upcoming-card.component.html',
  styleUrls: ['./upcoming-card.component.scss']
})
export class UpcomingCardComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

I've tried accessing the list using @ContentChildren, having the template structure properly for this case as well as using the proper lifecycle hook, but it results in the same. I can update styles by injecting the element ref and renderer into the child, but I want to do it at the parent level.

2

There are 2 answers

1
kellermat On BEST ANSWER

Thanks to a small modification of your code, I was eventually able to access the nativeElement of the ViewChildren. I passed the argument {read: ElementRef} to @ViewChildren and from then on item.nativeElement was no longer undefined.

Please see my code-snippet below or check out my working example on stackblitz where I accessed two nav-bars via @ViewChildren and colored them red.

  // {read: ElementRef} seems to be crucial in accessing nativeElement of a component:
  @ViewChildren('caroItem', {read: ElementRef})
  caroItems!: QueryList<ElementRef<UpcomingCardComponent>>;

  constructor(private renderer: Renderer2) {}

  ngAfterViewInit(): void {
    this.caroItems.forEach(item => {
      this.renderer.setStyle(item.nativeElement, 'background-color', 'red');
    })
  }
3
meriton On

It would be vastly easier to set the style using data binding:

<app-upcoming-card
      *ngFor="let card of list; let i = index"
      [style.background-color]="i < 2 ? 'red' : 'white'"
      ></app-upcoming-card>

(example highlights the first two items in red; adjust the conditions to whatever you wish to accomplish)