Cover
Jetzt kostenlos starten Webframeworks_les5.pdf
Summary
# Angular signals
Angular Signals bieden een modern, voorspelbaar reactiviteitsmodel dat oude beperkingen van Zones en RxJS aanvult of vervangt, door een fijnmazig trackingsysteem te introduceren dat exact weet waar state wordt gebruikt, waardoor rendering enkel plaatsvindt waar nodig. Dit is vergelijkbaar met `useState()` in React of het reactiviteitssysteem van Vue, en is volledig typesafe, synchroon en Angular-native [4](#page=4).
## 1.1 Signals vergeleken met gewone variabelen
Gewone variabelen bewaren enkel data, waardoor Angular moet raden welke delen van de UI hertekend moeten worden. Bij het gebruik van gewone variabelen triggert een wijziging in Angular vaak een volledige re-rendering van de component. Signals daarentegen zijn reactieve waarden die automatisch wijzigingen doorgeven aan hun "gebruikers". Dit resulteert in een efficiënter proces waarbij Angular enkel de relevante delen van de UI update die afhankelijk zijn van de gewijzigde signal. Signals bieden hierdoor traceerbaarheid en efficiëntie, omdat Angular precies weet wie "luistert" naar een bepaalde state [5](#page=5).
## 1.2 Signals vergeleken met Observables
Signals zijn synchroon en pull-based: de component leest de waarde van een signal wanneer deze nodig is. Observables zijn daarentegen asynchroon en push-based: de data stuurt nieuwe waarden over tijd naar de consument. Signals zijn ideaal voor het beheren van UI-state en afgeleide waarden binnen componenten. Observables zijn beter geschikt voor gebeurtenissen (events), datastromen (streams) en asynchrone data, zoals de resultaten van HTTP-verzoeken. Samen vormen Signals en Observables een complementair ecosysteem binnen Angular [6](#page=6).
## 1.3 Wat zijn Signals?
Een Signal is een wrapper rond een waarde die automatisch "gebruikers" verwittigt wanneer de onderliggende waarde verandert. Een signal wordt gelezen via een getterfunctie (bijvoorbeeld `mijnSignal()`), waardoor Angular bijhoudt waar het signal wordt gebruikt. Signals kunnen `writable` (schrijfbaar) of `readonly` (alleen-leesbaar) zijn, wat ze ideaal maakt voor het veilig delen van state tussen componenten en services [7](#page=7).
### 1.3.1 Writable Signals
Writable Signals kunnen rechtstreeks worden bijgewerkt en houden een waarde bij die zowel gelezen als gewijzigd kan worden. Ze hebben het type `WritableSignal` en worden vaak gebruikt voor lokale state binnen componenten of services. Writable Signals worden aangemaakt met de `signal()` functie, waarbij een initiële waarde wordt meegegeven [8](#page=8).
> **Tip:** Je wijzigt de waarde van een writable signal op twee manieren: `set()` vervangt de volledige waarde, terwijl `update()` de waarde functioneel aanpast. Telkens wanneer een signal wijzigt, weet Angular automatisch welke views moeten worden hertekend. Dit maakt ze ideaal voor interactieve UI-state zoals counters, toggles of filters [9](#page=9).
**Voorbeeld writable signal in de template:**
```html
`. Dit signaal is alleen leesbaar voor de buitenwereld en voorkomt ongewenste mutatie van state door andere delen van de applicatie. Het typische patroon is om een interne writable signal te hebben (bijvoorbeeld `_count`) en deze via `asReadonly()` beschikbaar te maken als een read-only signaal (`count`). Dit patroon zorgt ervoor dat de controle over waar en wanneer de state mag veranderen, centraal blijft binnen de betreffende service of klasse [29](#page=29).
---
# State management met signals
Signals bieden een krachtige en efficiënte manier om state management te implementeren, waarbij data centraal wordt beheerd om voorspelbaarheid en overzichtelijkheid te garanderen. Het hoofddoel is het creëren van één bron van waarheid per feature, verpakt in goed afgebakende "stores" (#page=31,32). Deze stores bevatten de state (via signals), bieden een eenvoudige API met selectors (computed signals) en actions (methoden). Encapsulatie is hierbij cruciaal: de state is intern schrijfbaar door de store, maar extern alleen leesbaar (#page=32,44). Dit resulteert in een voorspelbare dataflow waarbij actions de state muteren, selectors afgeleide state berekenen, en effects input/output operaties uitvoeren [31](#page=31) [32](#page=32) [44](#page=44).
### 2.1 Het store patroon met signals
Signals maken complexe stateflows overzichtelijk, omdat wijzigingen automatisch worden doorgegeven aan alle afhankelijke UI-elementen. De kern van dit patroon bestaat uit signals die de state vertegenwoordigen [34](#page=34).
> **Tip:** Het gebruik van signals voor state management zorgt ervoor dat UI-elementen die afhankelijk zijn van bepaalde state-waarden, automatisch worden bijgewerkt wanneer die state verandert [34](#page=34).
### 2.2 Selectors als computed signals
In plaats van traditionele getters worden selectors geïmplementeerd als `computed()` signals. Dit betekent dat selectors gememoïzeerd en 'lazy' zijn; ze worden pas opnieuw berekend wanneer hun onderliggende state daadwerkelijk verandert. De UI kan direct binden aan deze selectors, wat leidt tot declaratievere code. Een voorbeeld hiervan is `{{ store.visible().length }}` wat de lengte van een gefilterde lijst direct weergeeft [35](#page=35).
### 2.3 State persistentie
Kleine stores kunnen data eenvoudig lokaal opslaan met behulp van `localStorage`. Om deze persistentie te automatiseren, kan een `effect(() => this.persist())` worden gebruikt, waardoor de state automatisch wordt opgeslagen bij elke wijziging [36](#page=36) [37](#page=37).
> **Example:** Een winkelmandje-store kan zijn inhoud persistent opslaan, zodat deze behouden blijft wanneer de gebruiker de pagina verlaat en later terugkeert.
### 2.4 Asynchrone state met externe API's
Het store-patroon kan ook worden uitgebreid om asynchrone data te beheren die wordt opgehaald via externe API's. Dit kan worden bereikt met behulp van een `HttpClient` of `toSignal()`. Het is aan te raden om expliciete state-eigenschappen toe te voegen voor de laadstatus (`loading`) en eventuele fouten (`error`). Dit stelt de UI in staat om de gebruiker feedback te geven over de status van de data-ophaling [38](#page=38).
[Hier zou een visuele representatie van de async state flow kunnen staan, gebaseerd op pag. 39-40
### 2.5 Alternatieve methoden voor data loading
Voor eenvoudige dataloading scenario's, waarbij geen complexe state management of meerdere acties nodig zijn, bestaan er modernere alternatieven. Als de voorkeur uitgaat naar het vermijden van `subscribe()` aanroepen, bijvoorbeeld om geheugenlekken te voorkomen, kunnen andere benaderingen worden overwogen. Echter, deze zijn primair geschikt voor simpele dataloading zonder extra state-management (zoals `loading` en `error`), en minder voor uitgebreide stores die meerdere acties beheren [41](#page=41).
### 2.6 Feature stores en compositie
Het aanbevolen patroon is om één store per domein te creëren, zoals een `AuthStore`, `ProductsStore`, of `CartStore`. Deze feature stores kunnen elkaar injecteren en gezamenlijk afgeleide state delen, wat modulaire en onderhoudbare code bevordert [43](#page=43).
### 2.7 Encapsulatie en read-only toegang
Een essentieel aspect van het state management met signals is encapsulatie, waarbij de store verantwoordelijk is voor het schrijven naar zijn eigen state, terwijl de buitenwereld alleen leesrechten heeft. Dit principe, "buitenwereld leest, store schrijft", zorgt voor voorspelbaarheid en veiligheid, omdat de state-mutaties centraal en gecontroleerd plaatsvinden [44](#page=44).
---
# CRUD-operaties met signals
Dit onderwerp behandelt de praktische toepassing van Signals voor het beheren van data via Create, Read, Update en Delete (CRUD) operaties, zowel in een lokale opslag als via externe API's, met aandacht voor optimistische updates en het gebruik van computed signals.
### 3.1 Inleiding tot CRUD met Signals
CRUD staat voor Create, Read, Update, en Delete, de fundamentele operaties die nodig zijn voor datamanagement. In de context van state management en Signals, illustreert CRUD hoe data wordt aangemaakt, geladen of gefilterd, aangepast en verwijderd. Deze operaties bouwen voort op de patronen die worden gebruikt voor state management, waarbij `signal()` voor de state zelf, `computed()` voor afgeleide data en `effect()` voor opslag of synchronisatie wordt ingezet [42](#page=42) [46](#page=46).
### 3.2 CRUD in een Lokale Store
Het beheren van data lokaal, bijvoorbeeld met `localStorage`, maakt gebruik van Signals om CRUD-operaties te implementeren [47](#page=47).
#### 3.2.1 Aanmaken (Create)
Het toevoegen van een nieuw item aan een lijst wordt gerealiseerd door middel van een `create`-functie die het nieuwe item aan de bestaande lijst in een signal toevoegt [47](#page=47).
#### 3.2.2 Lezen (Read)
Het lezen van data omvat het ophalen van bestaande items uit de store. Dit kan ook het filteren van data omvatten, wat vaak wordt gedaan met `computed()` signals [47](#page=47) [54](#page=54).
#### 3.2.3 Bijwerken (Update)
Het aanpassen van een bestaande entry vereist het vinden van het specifieke item en het bijwerken van de data binnen de signal [47](#page=47).
#### 3.2.4 Verwijderen (Delete)
Het verwijderen van een item uit de store behelst het identificeren en uitsluiten van het betreffende item uit de signal die de lijst representeert [47](#page=47).
#### 3.2.5 Implementatie in Componenten en Templates
Deze CRUD-functies worden in componenten geïmplementeerd en kunnen direct in templates worden gebruikt. De volledige reactiviteit via Signals zorgt ervoor dat de UI automatisch wordt bijgewerkt zonder de noodzaak van de `async` pipe [49](#page=49) [50](#page=50).
### 3.3 CRUD via Externe API
Wanneer data wordt beheerd via een externe API, worden de CRUD-operaties vertaald naar HTTP-verzoeken [51](#page=51).
#### 3.3.1 Create met Externe API
Een `create`-operatie correspondeert met een POST-verzoek naar de API om nieuwe data te verzenden [51](#page=51).
#### 3.3.2 Read met Externe API
Het `read`-proces omvat meestal een GET-verzoek om data van de API op te halen [51](#page=51).
#### 3.3.3 Update met Externe API
Een `update`-actie wordt afgehandeld door een PUT- of PATCH-verzoek naar de API te sturen om bestaande data te wijzigen [51](#page=51).
#### 3.3.4 Delete met Externe API
Het `delete`-verzoek wordt omgezet in een DELETE-verzoek naar de API om data te verwijderen [51](#page=51).
### 3.4 Optimistische Updates en Rollback
Optimistische updates bieden een responsieve gebruikerservaring door de UI lokaal bij te werken voordat de serverrespons arriveert [53](#page=53).
* **Lokale Update:** De wijziging wordt direct doorgevoerd in de lokale state [53](#page=53).
* **Rollback:** Indien het API-verzoek faalt, wordt de lokale wijziging teruggedraaid naar de vorige staat [53](#page=53).
* **Feedback:** In combinatie met `loading`- en `error`-signals kan de gebruiker feedback ontvangen over de status van de bewerking [53](#page=53).
### 3.5 CRUD en Computed Signals
`Computed()` signals zijn bijzonder nuttig voor het afleiden van data die voortkomt uit CRUD-operaties, zoals het filteren van data. Dit is ideaal voor het implementeren van [54](#page=54):
* **Filters:** Dynamisch selecteren van items op basis van criteria [54](#page=54).
* **Totalen:** Berekenen van samenvattende waarden, zoals het aantal items of een som [54](#page=54).
* **Validatie:** Controleren van de status of integriteit van de data [54](#page=54).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Teller: {{ counter() }}
``` ### 1.3.2 Computed Signals Computed Signals berekenen hun waarde op basis van andere signals. Ze worden aangemaakt met de `computed()` functie, waarin de logica voor de afgeleide waarde wordt gedefinieerd. Angular volgt automatisch de afhankelijkheden van een computed signal; wanneer een bron-signal verandert, herberekent het computed signal zijn waarde. Computed signals zijn uitermate geschikt voor afgeleide UI-state, zoals totalen, gefilterde lijsten of andere afgeleide data [11](#page=11). **Voorbeeld Computed Signal:** Stel dat je een computed signal `total` hebt die afhangt van `price` en `quantity`: ```typescript const price = signal [10](#page=10); const quantity = signal [5](#page=5); const total = computed(() => price() * quantity()); ``` Angular weet dat `total` afhankelijk is van `price` en `quantity` en herberekent de waarde alleen wanneer deze bron-signals veranderen [12](#page=12). #### 1.3.2.1 Lazy/Memoized Computed Signals De functie die binnen `computed()` wordt gedefinieerd, wordt pas uitgevoerd wanneer het signal voor de eerste keer wordt gelezen. De berekende waarde wordt vervolgens gecachet. Bij een volgende lezing levert Angular de gecachete waarde terug zonder opnieuw te rekenen. Wanneer een afhankelijk signal verandert, markeert Angular de cache als "ongeldig" en herberekent de waarde pas opnieuw bij de volgende lezing. Dit mechanisme maakt het mogelijk om veilig complexere berekeningen uit te voeren, zoals filters of map-operaties, zonder performance-impact bij elke kleine wijziging [13](#page=13). #### 1.3.2.2 Read-only Computed Signals Een computed signal is per definitie read-only; je kunt geen `set()` of `update()` methoden gebruiken om de waarde ervan direct te wijzigen. Dit voorkomt onduidelijke dataflows, aangezien alle wijzigingen alleen via de bron-signals mogen plaatsvinden. `computed()` moet uitsluitend worden gebruikt voor pure functies zonder side-effects. Voor acties zoals logging of netwerkverkeer, die side-effects hebben, moet `effect()` worden gebruikt [14](#page=14). ### 1.3.3 Effect Signals Effects worden gebruikt voor het afhandelen van side-effects, zoals logging, interactie met `localStorage`, API-calls of DOM-manipulaties. Een effect voert automatisch code uit wanneer één of meer signals waarop het steunt, veranderen. Het verschilt van `computed()` in die zin dat `computed()` een nieuwe waarde berekent (puur, zonder bijwerkingen), terwijl `effect()` iets doet in de buitenwereld (zoals printen, opslaan of updaten). Een effect wordt minimaal één keer uitgevoerd en daarna opnieuw wanneer de afhankelijke signals veranderen [15](#page=15). **Voorbeeld effect():** ```typescript @Component({...}) export class MyComponent { userName = signal('Jan'); constructor() { effect(() => { console.log(`De gebruikersnaam is nu: USD{this.userName()}`); localStorage.setItem('lastUserName', this.userName()); }); } } ``` #### 1.3.3.1 Hoe werkt effect()? Een effect "luistert" automatisch naar de signals die erin worden gelezen. Als een van deze signals verandert, wordt het effect opnieuw uitgevoerd. Angular voert effects asynchroon uit tijdens de change detection cyclus, na de updates van de state. Meestal wordt een effect aangemaakt in de constructor van een component of service. Er is geen noodzaak om handmatig te subscriben of unsubscriben, aangezien Angular dit zelf regelt [17](#page=17). #### 1.3.3.2 Wanneer (niet) effect()? * **Wel:** Logging of debuggen van data, opslaan in `localStorage`, custom DOM- of canvas-updates, samenwerking met externe libraries [18](#page=18). * **Niet:** State-propagatie of updates van andere signals. Gebruik hiervoor `computed()` [18](#page=18). #### 1.3.3.3 Levensduur van effect() Effects worden automatisch opgeruimd wanneer hun bijbehorende component wordt verwijderd. Voor handmatige opruiming kan een cleanup functie worden geretourneerd vanuit het effect [19](#page=19). ## 1.4 Signals en Observables als partners Signals en Observables worden niet als concurrenten gezien, maar als partners die elkaar aanvullen. Signals beheren lokale en afgeleide UI-state, terwijl Observables asynchrone datastromen verwerken zoals HTTP-verzoeken, websockets of events. Samen faciliteren ze een vloeiende dataflow: data komt binnen via Observables en wordt vervolgens getoond en afgeleid met behulp van Signals. Ze worden vaak tegelijkertijd gebruikt via de functies `toSignal()` en `toObservable()` [20](#page=20). ### 1.4.1 Gebruikssituaties voor Signals en Observables | Situatie | Signals | Observables | | :---------------------------- | :----------------------------------------- | :---------------------------------------- | | Lokale UI-state | Teller, filter, formulier data | | | Afgeleide berekening | Totaalprijs, validatie | | | Data over tijd | | HTTP, websockets, interval | | Event-stream of user-actions | | Clicks, keypress | | Samenwerking tussen componenten | Componenten/services delen state | | ### 1.4.2 Observable naar Signal (toSignal()) De `toSignal()` functie converteert een Observable naar een Signal, waardoor de UI automatisch wordt bijgewerkt wanneer het Observable een nieuwe waarde emitteert. In de template kan de waarde van het resulterende signal vervolgens direct worden gebruikt met de getter syntax (bijv. `{{ time() }}`), zonder de noodzaak van de `async` pipe [22](#page=22). > **Tip:** `toSignal()` gedraagt zich vergelijkbaar met de `async` pipe, maar kan overal worden gebruikt, niet alleen in templates. Angular beheert automatisch het subscriben en unsubscriben bij vernietiging van de component of service. Gebruik `initialValue` (of `requireSync: true` bij `BehaviorSubject`s) om te voorkomen dat het signal `undefined` teruggeeft. Het is aan te raden om één enkele `toSignal()` per Observable te gebruiken en het resultaat te hergebruiken [23](#page=23). ### 1.4.3 Signal naar Observable (toObservable()) De `toObservable()` functie converteert een Signal naar een Observable die emitteert telkens wanneer het Signal wijzigt. Dit is handig wanneer je RxJS-operators wilt toepassen, zoals `switchMap`, `debounceTime`, of `combineLatest`. Angular creëert hiervoor intern een effect dat automatisch wordt opgeruimd [24](#page=24). > **Tip:** De Observable die uit `toObservable()` voortkomt, emitteert slechts de laatste stabiele waarde van het signal. Als er snel meerdere `.set()` calls worden gedaan, zal de Observable pas emitten na stabilisatie en alleen de laatste waarde doorgeven. Dit maakt `toObservable()` efficiënt en voorkomt overbodige RxJS-events [25](#page=25). ## 1.5 Gedeelde State met Signals Signals maken het eenvoudig om state te delen tussen componenten, bijvoorbeeld voor gedeelde gebruikersinformatie, een winkelmandje of thema-instellingen. Dit kan worden geïmplementeerd via een Angular service, die fungeert als een singleton en beschikbaar is via dependency injection. Met Signals kan state direct in de service worden opgeslagen en automatisch worden gedeeld tussen alle componenten die die service injecteren. Voor eenvoudige tot middelgrote applicaties is dit een krachtig alternatief voor `BehaviorSubject`, de `async` pipe, of NgRx (RxJS) [26](#page=26). **Voorbeeld van een state mini-store service:** ```typescript import { Injectable, signal } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; @Injectable({ providedIn: 'root', }) export class CounterService { private _count = signal; // Bron-signal . public count = this._count.asReadonly(); // Readonly signal voor consumptie increment() { this._count.update(value => value + 1); } decrement() { this._count.update(value => value - 1); } } ``` In componenten kan deze gedeelde state vervolgens eenvoudig worden gebruikt: ```htmlGedeelde teller: {{ counterService.count() }}
``` Alle componenten die dezelfde `CounterService` injecteren, delen dezelfde `count` state, en elke wijziging wordt automatisch overal doorgevoerd. Dit elimineert de noodzaak voor `@Input()` en `@Output()` decoratoren voor dergelijke scenario's [28](#page=28). ### 1.5.1 Readonly Signals De `asReadonly()` methode, aangeroepen op een writable signal, creëert een `ReadonlySignalGlossary
| Term | Definition |
|------|------------|
| Signals | Reactieve waarden in Angular die een automatisch bijwerkingsmechanisme bieden wanneer hun data verandert, wat leidt tot efficiëntere UI-rendering. |
| Change detection | Het proces binnen Angular waarbij het framework bijhoudt welke delen van de applicatie state zijn veranderd en bijgewerkt moeten worden in de UI. |
| Reactiviteitsmodel | Een programmeerparadigma waarbij veranderingen in data automatisch worden doorgegeven aan elementen die er afhankelijk van zijn, wat een dynamische en responsieve applicatie creëert. |
| RxJS | Een bibliotheek voor reactief programmeren met behulp van Observables, die gebruikt wordt voor het omgaan met asynchrone datastromen en complexe event-gedreven programmering. |
| Zones | Een oudere Angular-technologie die helpt bij het detecteren van asynchrone operaties en het triggeren van change detection; Signals bieden een moderner en fijnermaziger alternatief. |
| WritableSignal | Een type Signal in Angular dat direct kan worden aangepast met behulp van `set()` of `update()` methoden, ideaal voor het beheren van lokale state binnen componenten of services. |
| Computed Signal | Een type Signal waarvan de waarde wordt berekend op basis van andere Signals. Deze waarde wordt automatisch herberekend wanneer een van de afhankelijke Signals verandert en de berekende waarde wordt gecachet voor efficiëntie. |
| Effect | Een Angular-functie die wordt gebruikt voor side effects, zoals logging, opslaan in localStorage, of het direct manipuleren van de DOM. Een effect wordt automatisch opnieuw uitgevoerd wanneer een van de Signals waarop het steunt, verandert. |
| Async Pipe | Een Angular-pipe die de waarde van een Observable of Promise in de template automatisch abonneert en weergeeft, en zich automatisch uitschrijft wanneer de component wordt vernietigd. |
| State Management | Het proces van het organiseren, beheren en bijwerken van de data (state) van een applicatie op een gestructureerde en voorspelbare manier. |
| Store | Een centrale plek waar gerelateerde state wordt opgeslagen en beheerd, vaak met behulp van Signals, om een "single source of truth" te creëren. |
| Selectors | Functies of computed Signals die worden gebruikt om specifieke delen van de state op te halen of af te leiden, waardoor de toegang tot en het gebruik van de state wordt vereenvoudigd. |
| CRUD | Een acroniem dat staat voor Create, Read, Update, Delete, de vier fundamentele operaties die typisch worden uitgevoerd op data in een database of datastore. |
| Optimistic Updates | Een UI-strategie waarbij de applicatie de wijziging lokaal doorvoert voordat de serverreactie is ontvangen, wat resulteert in een responsievere gebruikerservaring. |
| Rollback | Het proces van het terugdraaien van een recente wijziging in de applicatiestate, meestal als reactie op een fout of mislukking van een operatie. |