Show cover

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 ?! :D 

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 :D 

@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();
 }