Les signals - Angular 16 - Les nouveautés

Ca y est la préparation de la version 16 d’angular est lancée ! Et une grande nouveauté ce sont les Signaux ! Où comment se passer de rxjs ? Et peut-être même du detect changes, onchanges hook ?

D’où ça vient ?

Voyons d’abord d’où ça vient. C’est très inspiré de SolidJs. Où leur utilité est quasi une pierre de la librairie.

En gros, c’est comme du rxjs behavior subject, sans rxjs, et le subscribe ! Dingue non ?

Voyons comment mettre ça en place !

Décortiquons le signal

On crée signal avec : const store = signal<type>(valeur par defaut).

On next une valeur avec : store.set(valeur)

On update une valeur (projection) avec : store.update(item => item + x)

Toute comparaison avec du redux, ou un gestionnaire d’état serait .. fortuite ?! 😀

Signal, Avec un service

Créons un service

@Injectable({
  providedIn: 'root'
})
export class CounterService {
  count = signal(0);

  init(value = 10): void {
    this.count.set(value);
  }

  increase(value = 1): void {
    this.count.update(item => item + value);
  }
}

Appelons ce service dans notre composant Angular 16

export class DiscoverSignalsComponent {
  private readonly service = inject(CounterService);
  counter = this.service.count;
}

Puis appelons le côté template js (euh html :D)

<div>
  {{ counter() }}
</div>

Noter l’appel de la fonction. (important pour la récup du signal)

Pour increase, il suffit de set, ou update :

// template
<button (click)="init()">Init</button>
<button (click)="increase()">Increase</button>

// component.ts
increase(): void {
    this.service.increase();
}

// service 
increase(value = 1): void {
    this.count.update(item => item + value);
}

Signal, en constante

On peut même aller plus loin en se passant de service

import { effect, signal } from "@angular/core";

export const counter = signal(0);

const itemEffect = effect(() => {
  console.log('MyValue changed', counter());
});


export const init = (value = 10) => {
  counter.set(10);
}

export const increase = (value = 1) => {
  counter.update(item => item + value);
}

Tu noteras ici, l’effect. Il permet de s’attacher à toute modification de l’état, via le set ou l’update sur le signal <3 !

Et ça nous donne côté composant :

// html
<div>
  {{ counter() }}
</div>

<button (click)="init()">Init</button>
<button (click)="increase()">Increase</button>

// component.ts
export class DiscoverSignalsComponent {
  // private readonly service = inject(CounterService);
  // counter = this.service.count;
  counter = counter;

  init(): void {
    // this.service.init();
    init();
  }

  increase(): void {
    // this.service.increase();
    increase();
  }
}

Sexy, non ?! 🙂

Signal, fetch et gestionnaire d’état

Aller on va plus loin ! Créons une sorte de mini store, et appelons une liste de planètes, depuis l’api starwars api 🙂

On crée notre store, avec de l’injection (injection token), et une fonction factory :

export function createStore() {
  const store = signal<ApiPlanetsResult>({ results: [] });

  const selectorAll = () => store;

  const loadAll = async () => {
    const response = await fetch('https://swapi.dev/api/planets');
    store.set(await response.json());
  }

  return {
    selectorAll,
    loadAll
  };
}

export const PLANETS_STORE = new InjectionToken<ReturnType<typeof createStore>>('Planets store with Signals');

export function provideTodosStore() {
    return { provide: PLANETS_STORE, useFactory: createStore };
}

Puis on l’appelle depuis notre composant typescript 😀

@Component({
  selector: 'app-list-planets',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './list-planets.component.html',
  styleUrls: ['./list-planets.component.css'],
  providers: [
    provideTodosStore()
  ]
})
export class ListPlanetsComponent implements OnInit {
  store = inject(PLANETS_STORE);
  state = this.store.selectorAll();

  ngOnInit(): void {
    this.store.loadAll();
  }
}

Tu noteras ici l’injection de notre token 🙂 et donc on récupère notre store

Et l’appel dans le composant est simplifié :

<select>
  <option *ngFor="let item of state().results" value="{{ item.name }}">{{item.name}}</option>
</select>

Le mot de la fin

Nous avons adoré chez DevToBeCurious les Signaux.

Ca nous a amené quand même des questions :

  • Quid de l’intéraction entre rxjs et signal ?
  • Et les stores (genre ngrx) vont-ils aller vers là ?
  • Niveau performance ? Plus rapide ?
  • Promise, rxjs, signal ? tous ensemble ?
  • Comment ça se passe avec le detectchanges ? Plus besoin de zone.js ?

Notre adresse

1 rue du guesclin
44000 Nantes

Notre téléphone

+33 2 79 65 52 87

Société

DevToBeCurious SARL
84860163900018 - Nantes B 848 601 639