6. Adding a Simple Store

🔀 Branch: For this lab, please switch to the branch 07-state-service-starter:

git reset --hard
git checkout 07-state-service-starter

In this lab, you add a Service acting as a simple store that supports unidirectional dataflow.

  1. Open the file dessert.store.ts (src/app/data/dessert.store.ts) that was added to this branch. It contains a simple store. Have a look at it and find out which properties and methods it provides.

  2. Switch to the file desserts.component.ts (src/app/desserts/desserts.component.ts), inject the store, and use it for providing the needed properties and methods:

     import { JsonPipe } from '@angular/common';
    -import {
    -  ChangeDetectionStrategy,
    -  Component,
    -  computed,
    -  inject,
    -  signal,
    -} from '@angular/core';
    +import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
     import { FormsModule } from '@angular/forms';
    -import { Dessert } from '../data/dessert';
     import { DessertFilter } from '../data/dessert-filter';
    -import { DessertService } from '../data/dessert.service';
    -import { DessertIdToRatingMap, RatingService } from '../data/rating.service';
    +import { DessertStore } from '../data/dessert.store';
     import { DessertCardComponent } from '../dessert-card/dessert-card.component';
    -import { ToastService } from '../shared/toast';
    +import { FormUpdateDirective } from '../shared/form-update.directive';
     
     @Component({
       selector: 'app-desserts',
       standalone: true,
    -  imports: [DessertCardComponent, FormsModule, JsonPipe],
    +  imports: [DessertCardComponent, FormsModule, JsonPipe, FormUpdateDirective],
       templateUrl: './desserts.component.html',
       styleUrl: './desserts.component.css',
       changeDetection: ChangeDetectionStrategy.OnPush,
     })
     export class DessertsComponent {
    -  #dessertService = inject(DessertService);
    -  #ratingService = inject(RatingService);
    -  #toastService = inject(ToastService);
    +  #store = inject(DessertStore);
     
    -  originalName = signal('');
    -  englishName = signal('');
    -  loading = signal(false);
    +  originalName = this.#store.originalName;
    +  englishName = this.#store.englishName;
    +  loading = this.#store.loading;
     
    -  desserts = signal<Dessert[]>([]);
    -  ratings = signal<DessertIdToRatingMap>({});
    -  ratedDesserts = computed(() => this.toRated(this.desserts(), this.ratings()));
    +  ratedDesserts = this.#store.ratedDesserts;
     
       constructor() {
    -    this.search();
    +    this.#store.loadDesserts();
       }
     
       search(): void {
    -    const filter: DessertFilter = {
    -      originalName: this.originalName(),
    -      englishName: this.englishName(),
    -    };
    -
    -    this.loading.set(true);
    -
    -    this.#dessertService.find(filter).subscribe({
    -      next: (desserts) => {
    -        this.desserts.set(desserts);
    -        this.loading.set(false);
    -      },
    -      error: (error) => {
    -        this.loading.set(false);
    -        this.#toastService.show('Error loading desserts!');
    -        console.error(error);
    -      },
    -    });
    -  }
    -
    -  toRated(desserts: Dessert[], ratings: DessertIdToRatingMap): Dessert[] {
    -    return desserts.map((d) =>
    -      ratings[d.id] ? { ...d, rating: ratings[d.id] } : d,
    -    );
    +    this.#store.loadDesserts();
       }
     
       loadRatings(): void {
    -    this.loading.set(true);
    -
    -    this.#ratingService.loadExpertRatings().subscribe({
    -      next: (ratings) => {
    -        this.ratings.set(ratings);
    -        this.loading.set(false);
    -      },
    -      error: (error) => {
    -        this.#toastService.show('Error loading ratings!');
    -        console.error(error);
    -        this.loading.set(false);
    -      },
    -    });
    +    this.#store.loadRatings();
       }
     
       updateRating(id: number, rating: number): void {
    -    this.ratings.update((ratings) => ({
    -      ...ratings,
    -      [id]: rating,
    -    }));
    +    this.#store.updateRating(id, rating);
    +  }
    +
    +  updateFilter(filter: DessertFilter): void {
    +    this.#store.updateFilter(filter);
       }
     }
    
  3. Open the file desserts.component.html (src/app/desserts/desserts.component.html) and switch from two way bindings with ngModel to property bindings:

    -<form>
    +<form (appUpdate)="updateFilter($event)">
       <div class="card-grid">
         <div>
           <label for="name">Original Name</label>
    -      <input [(ngModel)]="originalName" name="originalName" />
    +      <input [ngModel]="originalName()" name="originalName" type="text" />
         </div>
         <div>
           <label for="englishName">English Name</label>
    -      <input [(ngModel)]="englishName" name="englishName" />
    +      <input [ngModel]="englishName()" name="englishName" />
         </div>
       </div>
    
    [...]
    
    

    Hint: Use the FormUpdateDirective included in this branch to react to form changes.


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


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