2. Component Communication With Signals

🔀 Branch: For this lab, please switch to the branch 02-sub-comp-starter:

git reset --hard
git checkout 02-sub-comp-starter

2.1 Using Input Signals for Property Bindings

Now, let's use Signal-based inputs in our DessertCardComponent!

  1. Open the file dessert-card.component.ts (src/app/dessert-card/dessert-card.component.ts) and replace the traditional @Input with a required InputSignal using the function input.required:

    -import { Component, EventEmitter, Input, Output } from '@angular/core';
    +import { Component, input, output } from '@angular/core';
     import { Dessert } from '../data/dessert';
     import { RatingComponent } from '../rating/rating.component';
    
    [...]
    
       styleUrl: './dessert-card.component.css',
     })
     export class DessertCardComponent {
    -  @Input({ required: true })
    -  dessert!: Dessert;
    +  dessert = input.required<Dessert>();
     
    -  @Output()
    -  ratingChange = new EventEmitter<number>();
    +  ratingChange = output<number>();
     
       updateRating(newRating: number): void {
    -    this.dessert.rating = newRating;
         this.ratingChange.emit(newRating);
       }
     }
    
  2. Switch to the file dessert-card.component.html (src/app/dessert-card/dessert-card.component.html) and call the introduced InputSignals's getter:

     <div class="card">
    -  <h2 class="card-title">{{ dessert.originalName }}</h2>
    -  <i class="card-subtitle">{{ dessert.englishName }}</i>
    +  <h2 class="card-title">{{ dessert().originalName }}</h2>
    +  <i class="card-subtitle">{{ dessert().englishName }}</i>
     
       <img
         class="w-full mt-5"
    -    src="/assets/{{ dessert.image }}"
    +    src="/assets/{{ dessert().image }}"
         alt="Bildbeschreibung"
       />
     
       <div class="card-body mb-0">
    -    <span class="card-text">{{ dessert.description }}</span>
    +    <span class="card-text">{{ dessert().description }}</span>
       </div>
       <div class="card-footer">
    -    <div class="card-text mt-0 mb-5">{{ dessert.kcal }} kcal</div>
    +    <div class="card-text mt-0 mb-5">{{ dessert().kcal }} kcal</div>
         <app-rating
    -      [rating]="dessert.rating"
    +      [rating]="dessert().rating"
           (ratingChange)="updateRating($event)"
         ></app-rating>
       </div>
    


💾 Please find this lab's solution in the branch 02a-input-signals (get reset --hard && git checkout 02a-input-signals). You don't have to switch to it branch, if you are fine.

2.2 Using Model Signals for Two Way Bindings

Our RatingComponent provides a Two Way Binding. Let's also switch to Signals here!

  1. Open the file rating.component.ts (src/app/rating/rating.component.ts) and replace the @Input/@Output combination of rating/ratingChange with a sole ModelInput. For this, you can use the model function:

    -import {
    -  Component,
    -  EventEmitter,
    -  Input,
    -  OnChanges,
    -  Output,
    -} from '@angular/core';
    +import { Component, computed, model, signal } from '@angular/core';
     
     const maxRatingInCheatMode = 500;
    
    [...]
    
       templateUrl: './rating.component.html',
       styleUrl: './rating.component.css',
     })
    -export class RatingComponent implements OnChanges {
    -  @Input({ required: true })
    -  rating = 0;
    +export class RatingComponent {
    +  rating = model.required<number>();
     
    -  @Output()
    -  ratingChange = new EventEmitter<number>();
    +  cheatMode = signal(false);
     
    -  maxRating = 5;
    -  stars: Array<boolean> = [];
    -
    -  ngOnChanges(): void {
    -    if (this.rating > this.maxRating) {
    -      this.maxRating = maxRatingInCheatMode;
    -    }
    -    this.#updateStars();
    -  }
    -
    -  #updateStars() {
    -    this.stars = this.toStars(this.rating, this.maxRating);
    -  }
    +  maxRating = computed(() =>
    +    this.cheatMode() || this.rating() > 5 ? maxRatingInCheatMode : 5,
    +  );
    +  stars = computed(() => this.toStars(this.rating(), this.maxRating()));
     
       private toStars(rating: number, maxRating: number): Array<boolean> {
         const stars = new Array<boolean>(rating);
    
    [...]
    
       }
     
       rate(rating: number): void {
    -    this.rating = rating;
    -    this.#updateStars();
    -    this.ratingChange.next(rating);
    +    this.rating.set(rating);
       }
     
       enterCheatMode() {
    -    this.maxRating = maxRatingInCheatMode;
    -    this.#updateStars();
    +    this.cheatMode.set(true);
       }
     }
    

    Remarks: After switching to Signals, we need to derive the remaining component state (maxRating, starts) using computed. Also, add a further signal informing whether we are in cheat mode.

  2. Switch to the file rating.component.html (src/app/rating/rating.component.html) and call the star Signal's setter in the for loop:

    -@for (star of stars; track $index) {
    +@for (star of stars(); track $index) {
       @if (star) {
         <img
           src="/assets/star.svg"
    


💾 Please find this lab's solution in the branch 02b-model-signals (get reset --hard && git checkout 02b-model-signals). You don't have to switch to it branch, if you are fine.


> Follow us on X (Twitter) > More Trainings (German) > More Trainings (English)