Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Internal Magic of Angular Signals - iJS London 04/2024

The Internal Magic of Angular Signals - iJS London 04/2024

ANGULARarchitects.io - Michael Egger-Zikes

Since the release of Angular 17, the first part of the Signals implementation is now production-ready. Those APIs offer an additional concept besides RxJS to handle reactivity and influence the framework in many areas. Signals will lead to a different more fine-grained and more performant way of processing the Change Detection to update the view. All of this is added to be backward-compatible.

While using Signals feels simpler and more intuitive than RxJS subscription management, it isn't likely to replace RxJS in all areas. Since Signals are easy to work with, many developers are surprised how the “Magic of Angular Signals” is implemented behind the covers and why it is possible that computed values, effects, and Component Templates reprocess as dependent Signals change.

This talk will give insights into how Signals are implemented to make sure that they update correctly and no intermediate processing results are sent out.

Michael Egger-Zikes

April 11, 2024
Tweet

More Decks by Michael Egger-Zikes

Other Decks in Programming

Transcript

  1. Signal APIs Reactivity Signal Updates Internal Magic of Signals Reactive

    Nodes Reactive Context Signal Component Change Detection Michael Egger-Zikes (@MikeZks)
  2. About Michael • Michael Egger-Zikes ANGULARarchitects.io • Focus on Angular

    • Trainings, Consultancy, Reviews • Conference Speaker Public: in-person or remote In-House: everywhere angulararchitects.io /en/angular-workshops Michael Egger-Zikes @MikeZks
  3. Reactive Primitive • Reactive Primitive • Return the current value

    synchronously • Same value does not trigger update • Can update consuming logic on change automatically • Signals & RxJS • No direct replacement for RxJS, though it will become optional • Allows subscription management for Observable Streams • Easier to use in Templates & TypeScript Michael Egger-Zikes (@MikeZks)
  4. Derived State & Side-Effects • Computed Signal • Uses Signals

    als Inputs to create derived state • userStr = computed(() => 'User: ' + user().name) • Side Effects • Is executed again, if consuming Signals change • effect(() => console.log(user(), product())) Michael Egger-Zikes (@MikeZks)
  5. Signal API – already State Management? • WritableSignal • Signal

    is a Function and Object combined • user = signal({ name: 'michael.egger-zikes' }) • <p>{{ user().name }}</p> • Update Signal value • Overwrite • user.set({ name: 'manfred.steyer' }) • Immutable • user.update(u => ({ ...u, name: 'manfred.steyer' })) Michael Egger-Zikes (@MikeZks)
  6. Promise async no init value 1 event Observable sync/async init

    value optional 0, 1, n events JavaScript & Reactivity Signals sync inital value 1, n values Michael Egger-Zikes (@MikeZks)
  7. RxJS Subscription Michael Egger-Zikes (@MikeZks) const subscription = combineLatest({ clickTrigger:

    userClick$, // Trigger, State timeTrigger: timeBasedTrigger$ // Trigger }).pipe( withLatestFrom(state$), // State // Transformation map(([, state]) => transformToConsumerValue(state)) ).subscribe({ next: name => { /* my callback logic */ }, error: () => { /* my error processing */ }, complete: () => { /* my completion logic */} });
  8. Signal-based reactive Updates Michael Egger-Zikes (@MikeZks) effect(() => { //

    my callback logic rxForm.patchValue({ name: name(), // Trigger, State active: untracked(() => active()) // State }) });
  9. Signal-based non-reactive Read Michael Egger-Zikes (@MikeZks) function search(): Observable<string> {

    return http.get<string>( url, { params: { name: name() // Signal read (no update) }} ); }
  10. Not Active Defined, no Read Not Live Non-Reactive Read Signal

    Update Behavior Live Reactive Updates Michael Egger-Zikes (@MikeZks)
  11. Writable Signal Read/Write State Computed Signal Derived State ReactiveLViewConsumer Template

    Updates Reactive Nodes – Signals Deps Graph Watch Side-Effect Michael Egger-Zikes (@MikeZks)
  12. Writable Signal Read/Write State Computed Signal Derived State ReactiveLViewConsumer Template

    Updates Reactive Context – Reactive Updates Watch Side-Effect Michael Egger-Zikes (@MikeZks)
  13. from signal() Reactive Edge: Live Consumer Michael Egger-Zikes (@MikeZks) to

    signal() flightRoute computed() log(flightRoute()) effect() {{ flightRoute() }} Template Consumer Producer Producer Consumer Consumer Producer Producer
  14. effect() defined processed effect() assigns as Active Consumer Reactive Graph

    Creation effect() reads Signals Michael Egger-Zikes (@MikeZks) name = signal() checks Active Consumer name = signal() creates Reactive Graph name.set() notifies Consumers
  15. effect() defined processed effect() assigns as Active Consumer Non-Reactive Read

    effect() reads Signal w/ untracked() Michael Egger-Zikes (@MikeZks) active = signal() checks Active Consumer active = signal() no Active Consumer Non-Live active() Signal-Read returns value once
  16. Active Consumer Michael Egger-Zikes (@MikeZks) const COMPUTED_NODE = { producerRecomputeValue:

    (node: ComputedNode<unknown>): void => { const prevConsumer = consumerBeforeComputation(node); } }; let activeConsumer: ReactiveNode | null = null; function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode | null { const prev = activeConsumer; activeConsumer = consumer; return prev; } function consumerBeforeComputation(node: ReactiveNode | null): ReactiveNode|null { return setActiveConsumer(node); }
  17. Signal Updates • from() • Who is the current activeConsumer?

    • Check, if a Reactive Node is assigned • Reactive Graph for Live Consumer is created • from.set() • Producer notifies Consumer • Consumer may check the Version of the Value • Consumer may Read the Value Michael Egger-Zikes (@MikeZks)
  18. Signal-based Change Detection • Signal-based Change Detection • Fine-grained DOM

    updates on change only • Embedded View: @if, @for • Change Detection can run locally • NgZone will become optional • Current Optimizations • Version 16: for OnPush on Signal Update • marks Component and Partents for Check • like AsyncPipe • Version 17: for OnPush on Signal Update • marks specific Component with Signal-Read for Check only • Parents Components do not get checked automatically Michael Egger-Zikes (@MikeZks)
  19. Signal-based Components Michael Egger-Zikes (@MikeZks) Work w/o Zone.js Interop with

    traditional Components Fine-grained Change Detection Not forced to update the current codebase
  20. Signal-based Components • APIs for automatic Signal creation • flight

    = input() • Computed Signal • selected = model() • Proxy Signal • @for (flight of bookingFeature.flights(); track flight.id) { } • Reactive @for provides Signals to trigger the Embedded Views • Important for future browser updates • Signal-based Change Detection w/o Zone.js or combined with Zone.js • Signal-Reads trigger a Change Detection processing only Michael Egger-Zikes (@MikeZks)
  21. Signal Components APIs Michael Egger-Zikes (@MikeZks) @Component({ selector: 'app-flight-card', standalone:

    true, template: ` <h2>{{ flightRoute() }}</h2> <p>Date: {{ item().date }}</p> `, }) export class FlightCardComponent { item = input.required<Flight>(); // no inital value, but provided by parent selected = model(false); // writable Signal: this.select.set(true) → emits output delayTrigger = output<Flight>(); // creates EventEmitter → this.delayTrigger.emit(this.item()) flightRoute = computed(() => this.item().from + ' - ' + this.item().to); }