SlideShare a Scribd company logo
Angular Meetup
Angular v16, May 2023
The Rise of Angular Signals
Angular 16
"The biggest release since the initial rollout of Angular"
Yaron Biton
Misterbit CTO
Who am I?
Coding Academy
Chief Instructor
Angular 16
This release has a significant impact
on any Angular team
Let's deep dive into
how to code modern Angular apps
• Anything web, end to end projects
• Tech companies and startups
• Consulting to management and dev teams
• Available on demand coders
https://www.misterbit.co.il
• Advanced web-techs training: Angular, React,
Vue, Node, Modern architectures, etc.
• Catchup workshops for managers and leaders
• Coding Academy bootcamp
• Advanced selective training - best practices
• Hundreds of employed full-stack developers
every year
https://www.coding-academy.org
Angular 16 features
We can (finally) use self-enclosing-tags!
Becomes:
<super-duper-long-component-name [prop]="someVar">
</super-duper-long-component-name>
<super-duper-long-component-name [prop]="someVar"/>
Angular 16 features
We (finally) have a way to require a component
@Input:
Angular 16 features –
Rethinking NgModules
Tooling for standalone components, directives
and pipes
// The schematics convert an existing project
// remove unnecessary NgModules classes,
// and change the bootstrap of the project to use standalone APIs:
ng generate @angular/core:standalone
// new projects as standalone from the start:
ng new --standalone
// Creates a simpler project without any NgModules and with
// generators that produce standalone directives, components, and pipes
// Generate a standalone component
ng generate componenet --standalone cmpName
Rethinking the CLI –
(As most modern CLIs )
> ng serve
// Vite development server!
Angular CLI is now Vite!
Rethinking Unit-Testing
Moving Angular CLI to Jest
Rethinking Reactivity
Preface
• On February 2023, Angular's team introduced
Signals to the framework with a simple pull
request.
• Since then, there have been a storm in the Angular
community about its use and benefits
• …and if it’s another rewrite of the framework
• In a model-driven web application, one of the main jobs
of the framework is synchronizing changes to the
application's data model and the UI.
• We refer to this mechanism as reactivity, and every
modern web framework has its own reactivity system.
Reactivity with Signals
Welcome Signals
• In modern Angular every piece of important data is
wrapped and used as signals
• So, signals become immediately the most basic and
important feature in Angular
Signals
• Angular Signals have an initial
value
• When executed, they return the
current value of the signal
quantity_ = signal<number>(1)
selectedCar_ = signal<Car>(null)
userMsg_ = signal({ txt: '', type: '' })
cars_ = signal<Car[]>([])
<h5> Quantity: {{ quantity_() }} </h5>
<user-msg [msg]="userMsg_()"></user-msg>
Setting Signal value
The signal function returns a WritableSignal<T>
which allow modifying the value:
// Replace the signal value
const movies_ = signal<Movie[]>([])
movies_.set([{ name: 'Fight club' }])
// Derive a new value
const someCount_ = signal<Number>(0)
someCount_.update(n => n + 1)
// Perform internal mutation of arrays and other objects
movies_.mutate(list => {
list.push({name: 'Aba Ganuv'})
})
computed values
computed() creates a memoizing signal, which
calculates its value from some other signals
const counter_ = signal(0)
// Automatically updates when `counter` changes:
const isEven_ = computed(() => counter_() % 2 === 0)
The value of the computed signal is being
recalculated whenever any of it's dependencies
changes.
computed() Signal
• The computed() function returns a Signal and not a
WritableSignal, which means it cannot be manually
modified (with set, update, or mutate)
• Instead, it is updated automatically whenever one
of its dependent signals change.
export function computed<T>(
computation: () => T, equal: ValueEqualityFn<T> = defaultEquals): Signal<T>
const moviesCount_ = computed(() => movies_().length)
side effect()
effect() schedules and runs a side-effectful function
Signal dependencies of this function are captured,
and the side effect is re-executed whenever any of
its dependencies produces a new value.
const counter_ = signal(0)
effect(() => console.log('The counter is:', counter_()))
// The counter is: 0
counter_.set(1)
// The counter is: 1
Effects do not execute synchronously with the set but are scheduled and resolved by
the framework. The exact timing of effects is unspecified.
effect() use cases
Effects are useful in specific situations.
Here are some examples:
• Performing custom rendering to a <canvas>
• Charting library, or other third party UI
library
• Keeping data in sync with window.localStorage
Demo Time
Before going deeper let's play
with some sample code
effect() registration
• Registering a new effect with the effect() function
requires an "injection context" (access to the
inject function).
• So we call either:
• Create the effect within a constructor
• Assign the effect to a field
• Pass an Injector to effect via its options:
@Component({...})
export class EffectiveCounterCmp {
readonly count_ = signal(0)
constructor(private injector: Injector) {}
initializeLogging(): void {
effect(() => {
console.log(`The count is: ${this.count_()})`)
}, {injector: this.injector})
}
}
effect() registration caveat
Registering an effect in the wrong place, produces a
weird message:
ngOnInit() {
effect(() => console.log(JSON.stringify(this.cars_())))
}
Into Signals
• Signal is a reactive value and is a producer that
notify consumers(dependents) when it changes.
• Any code that has registered an interest in the
Signal’s value is tracked as dependent.
• When the Signal’s value is modified, the Signal
will notify all of its dependents, allowing them to
react to the change in the Signal’s value.
Adding dependents (consumer) to a Signal
• When we use a signal in our template, this
dependency is being added to that signal
• We can add consumers by using effect and computed
functions.
Signal effect destroy
• effects are automatically destroyed when their
enclosing context is destroyed
• The effect() function gets an onCleanup function and
returns an EffectRef, that can be used for manually
destroy
• destroy(): 🧹 Shut down the effect, removing it
from any upcoming scheduled executions.
• The signature of the effect function:
export function effect(
effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef
Signal equality functions
• When creating a signal, we can optionally provide
an equality function
• It will be used to check whether the new value is
actually different than the previous one
import _ from 'lodash'
const data_ = signal(['test'], {equal: _.isEqual})
// Using deep equality - signal won't trigger any updates
data_.set(['test'])
untracking
reading without tracking dependencies
// You can prevent a signal read from being tracked by calling its getter with untracked:
effect(() => {
console.log(`User set to `${ currentUser_() }` and the counter is ${untracked(counter_)}`)
})
// Another example - invoke some external code which shouldn't be treated as a dependency:
effect(() => {
const user = currentUser_()
untracked(() => {
// If the `loggingService` reads signals, they won't be counted as
// dependencies of this effect.
this.loggingService.log(`User set to ${user}`)
})
})
Into the RFC
Lets review some points
from the official docs
The downfall of Global,
top-down change detection
"Angular's default strategy is to run change detection
over the entire component tree to make sure that the
DOM reflects the most up-to-date model.
Because Angular has no information about which
parts of the application state have actually changed,
it must check everything.
In practice, however, only a fraction of the entire
application state changes and only a handful of
components need to be re-rendered."
The downfall of onPush
"While the OnPush strategy can reduce
some of the performance cost, this
strategy is (very) limited:
change detection always starts from the root component
Additionally - OnPush components prevent their
descendants from being checked, descendent components
that depend on global state are not updated."
The downfall of the single traversal
"Angular's change detection was designed to refresh
application state once. The change detection process starts
from the root of the component tree and walks all components
down to the leaf nodes.
However, many common web interaction patterns roll up
descendant node states into ancestor nodes (e.g. form validity
is computed as a function of descendant control states).
This leads to the most "popular" error in Angular -
ExpressionChangedAfterItHasBeenCheckedError."
The downfall of Zone.js
"Crucially, zone.js does not provide "fine-grained"
information about changes in the model.
Zone.js is only capable of notifying us when
something might have happened in the application,
and can give no information about what happened or
what has changed."
The downfall of Zone.js
• "Large applications often grow to see
zone.js become a source of performance
issues and developer-facing complexity.
• As the web platform continues to evolve, it
also represents a rising maintenance cost
for the Angular team."
The downfall of Zone.js
Zone Pollution
https://angular.io/guide/change-detection-zone-pollution
@Component(...)
class AppComponent {
constructor(private ngZone: NgZone) { }
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
// Sometimes we need to skip running change detection:
Plotly.newPlot('chart', data)
})
}
}
The downfall of Zone.js
async – await?...
WARNING: Zone.js does not support native async/await.
These blocks are not intercepted by zone.js and
will not trigger change detection.
See: https://github.com/angular/zone.js/pull/1140 for more information.
What about RxJS?
RxJS remains in HttpClient
The good side:
Makes it easy to design asycn patterns such as keeping one request per input
trigger (switchMap) or piping into other Rxjs operators
Which http calls should be made in parallel (`mergeMap`), cancel the one
ongoing and move to the new one (`switchMap`), make one after another has
finished (`concatMap`), etc etc.
The bad side:
Setting up streams for one-time ajax calls
RxJS Interop - takeUntilDestroy
It is very common to tie the lifecycle of an Observable to a
particular component’s lifecycle
// Here is a common Angular pattern:
destroyed$ = new ReplaySubject<void>(1)
data$ = http.get('...').pipe(takeUntil(this.destroyed$))
ngOnDestroy() {
this.destroyed$.next()
}
// Now, we can:
data$ = http.get('…').pipe(takeUntilDestroyed())
RxJS Interop
Convert a signal to observable:
import { toObservable, signal } from '@angular/core/rxjs-interop'
@Component({ ...})
export class App {
count_ = signal(0)
count$ = toObservable(this.count_)
ngOnInit() {
this.count$.subscribe(() => ...)
}
}
RxJS Interop
Convert an observable to a signal:
import { toSignal } from '@angular/core/rxjs-interop'
@Component({
template: `
<li *ngFor="let row of data_()"> {{ row }} </li>
`
})
export class App {
dataService = inject(DataService)
data_ = toSignal(this.dataService.data$, [])
}
Promise => Observable => Signal someone?
Simple counter signal
Here is a simple counter signal
@Component({
template: `
<div>Count: {{ count_() }}</div>
<div>Double: {{ doubleCount_() }}</div>
`
})
export class App {
count_ = signal(0)
doubleCount_ = computed(() => this.count_() * 2)
constructor() {
setInterval(() => this.count_.set(this.count_() + 1), 1000)
}
}
Simple RxJS counter
import { BehaviorSubject, map, take } from 'rxjs'
export class AppComponent {
template: `
<div>Count: {{ count$ | async }}</div>
<div>Double: {{ doubledCount$ | async }}</div>
`,
count$ = timer(0, 1000)
doubleCount$ = this.count$.pipe(map((v) => v * 2))
}
Here is a simple counter with RxJS:
RxJS is hard for humans
RxJS has a still learning curve, some developers would code it like that:
import { BehaviorSubject, map, take } from 'rxjs'
@Component({
selector: 'counter',
template: `
<div>Count: {{ count$ | async }}</div>
<div>Double: {{ doubleCount$ | async }}</div>
`
})
export class AppComponent {
count$ = new BehaviorSubject(0)
doubleCount$ = this.count$.pipe(map((value) => value * 2))
constructor() {
setInterval(() => {
let currentCount = 0
this.count$.pipe(take(1)).subscribe((x) => (currentCount = x))
this.count$.next(currentCount + 1)
}, 1000)
}
}
The downfall of Rxjs
• "Angular does not internally use RxJS to
propagate state or drive rendering in any
way.
• Angular only uses RxJS as a convenient
EventEmitter completely disconnected from
the change detection and rendering system"
• Amm.. what about async pipes?..
The downfall of Rxjs
RxJS is not glitch-free - It's easy to craft an example which shows this behavior:
import { BehaviorSubject, combineLatest, map } from 'rxjs'
const counter$ = new BehaviorSubject(0)
const isEven$ = counter$.pipe(map((value) => value % 2 === 0))
const message$ = combineLatest(
[counter$, isEven$],
(counter, isEven) => `${counter} is ${isEven ? 'even' : 'odd'}`
)
message$.subscribe(console.log)
counter$.next(1)
// 0 is even
// 1 is even ???
// 1 is odd
In asynchronous RxJS code, glitches are not typically an
issue because async operations naturally resolve at
different times.
Most template operations, however, are synchronous, and
inconsistent intermediate results can have drastic
consequences for the UI.
For example, an NgIf may become true before the data is
actually ready
The downfall of Rxjs
"Signals replace the currently used BehaviorSubject
With Signals, Subscriptions get created and destroyed
automatically under the hood. More or less what happens
using the async pipe in RxJS.
However, unlike Observables, Signals don’t require a
Subscription to be used outside the template."
The downfall of Rxjs
"Using the async pipe several times creates separate
Subscriptions and separate HTTP requests.
Developers need to be conscious about using a
shareReplay or avoid multiple subscribers to avoid
multiple calls and side effects.
The downfall of Immutability
"Signals work with both, we don't want to "pick sides" but
rather let developers choose the approach that works
best for their teams and use-cases.
Signal-based Components
@Component({
signals: true,
selector: 'user-profile',
template: `
<p>Name: {{ firstName_() }} {{ lastName_() }}</p>
<p>Account suspended: {{ suspended_() }}</p>
`,
})
export class UserProfile {
firstName_ = input<string>() // Signal<string|undefined>
lastName_ = input('Smith') // Signal<string>
suspended_ = input<boolean>(false, {
alias: 'disabled',
})}
Upcoming
Model inputs and two-ways-data-binding
@Component({
signals: true,
selector: 'some-checkbox',
template: `
<p>Checked: {{ checked() }}</p>
<button (click)="toggle()">Toggle</button>
`,
})
export class SomeCheckbox {
// Create a *writable* signal.
checked_ = model(false)
toggle() {
checked_.update(c => !c)
}
}
Model inputs and two-ways-data-binding
@Component({
signals: true,
selector: 'some-page',
template: `
<!-- Note that the getter is *not* called here,
the raw signal is passed -->
<some-checkbox [(checked)]="isAdmin_" />
`,
})
export class SomePage {
isAdmin_ = signal(false)
}
Outputs in signal-based components
@Component({
signals: true,
selector: 'simple-counter',
template: `
<button (click)="save()">Save</button>
<button (click)="reset()">Reset</button>
`,
})
export class SimpleCounter {
saved = output<number>() // EventEmitter<number>
cleared = output<number>({alias: 'reset'})
save() {
this.saved.emit(123)
}
reset() {
this.cleared.emit(456)
}
}
Signal based queries
@Component({
signals: true,
selector: 'form-field',
template: `
<field-icon *ngFor="let icon of icons()"> {{ icon }} </field-icon>
<div class="focus-outline">
<input #field>
</div>
`
})
export class FormField {
icons = viewChildren(FieldIcon) // Signal<FieldIcon[]>
input = viewChild<ElementRef>('field') // Signal<ElementRef>
someEventHandler() {
this.input().nativeElement.focus()
}
}
Life cycle hooks
• Zone-based components support eight different lifecycle
methods.
• Many of these methods are tightly coupled to the current
change detection model, and don't make sense for signal-
based components.
• Signal-based components will retain the following lifecycle
methods:
• ngOnInit
• ngOnDestroy
Life cycle hooks
To supplement signal components, three new
application-level lifecycle hooks:
function afterNextRender(fn: () => void): void
function afterRender(fn: () => void): {destroy(): void}
function afterRenderEffect(fn: () => void): {destroy(): void}
* Why not ChangeDetectionStrategy.Signals?..
Life cycle hooks
Here is an example:
@Component({
template: `
<p #p>{{ longText_() }}</p>
`,
})
export class AfterRenderCmp {
constructor() {
afterNextRender(() => {
console.log('text height: ' + p_().nativeElement.scrollHeight)
})
}
p_ = viewQuery('p')
}
Is this another Angular rewrite?
Hmmm…. yes
The Angular team takes backwards compatibility seriously
But many things change in the surface API
Don’t rush: While many library developers will work on signal support
we gonna dance the semver for some time
Consider Microfrontends where applicable
• Available on demand coders
• Advanced web-techs training
• Consulting and workshops for leaders
Contact us:
admin@misterbit.co.il
https://www.misterbit.co.il
https://www.coding-academy.org
Know anyone that is a good fit?
send'em our way!

More Related Content

PPTX
Introduction to CI/CD
PDF
TM Forum Frameworx Overview Course
PPTX
Angular overview
PDF
Introduction to CICD
PPTX
Angular 9
PDF
Angular - Chapter 1 - Introduction
PPTX
JAVA-PPT'S.pptx
PPTX
TypeScript Overview
Introduction to CI/CD
TM Forum Frameworx Overview Course
Angular overview
Introduction to CICD
Angular 9
Angular - Chapter 1 - Introduction
JAVA-PPT'S.pptx
TypeScript Overview

What's hot (20)

PPTX
Angular 5 presentation for beginners
PPTX
PPTX
Unit 1 - TypeScript & Introduction to Angular CLI.pptx
PPT
Angular Introduction By Surekha Gadkari
PPTX
Angular introduction students
ODP
Introduction to Swagger
PDF
Angular Dependency Injection
PPTX
Introducing type script
PPTX
Node.js Express
PDF
Angular data binding
PDF
Introduction to RxJS
PDF
Angular
PDF
Angular - Chapter 7 - HTTP Services
PPTX
Angular Basics.pptx
PPTX
Angular modules in depth
PDF
Angular components
PDF
Building blocks of Angular
PPTX
Angular Data Binding
PDF
Angular - Chapter 2 - TypeScript Programming
PDF
Angular Observables & RxJS Introduction
Angular 5 presentation for beginners
Unit 1 - TypeScript & Introduction to Angular CLI.pptx
Angular Introduction By Surekha Gadkari
Angular introduction students
Introduction to Swagger
Angular Dependency Injection
Introducing type script
Node.js Express
Angular data binding
Introduction to RxJS
Angular
Angular - Chapter 7 - HTTP Services
Angular Basics.pptx
Angular modules in depth
Angular components
Building blocks of Angular
Angular Data Binding
Angular - Chapter 2 - TypeScript Programming
Angular Observables & RxJS Introduction
Ad

Similar to Angular 16 – the rise of Signals (20)

PDF
Commit University - Exploring Angular 2
PPTX
Angular js
PDF
Developing maintainable Cordova applications
PPTX
devjam2018 - angular 5 performance
PDF
Angular performance slides
PDF
From User Action to Framework Reaction
PPTX
Angular Js Basics
PDF
Declarative presentations UIKonf
PDF
Taming event-driven software via formal verification
PDF
From User Action to Framework Reaction
PDF
Angular2 with type script
PDF
Angular Optimization Web Performance Meetup
PDF
Angular meetup 2 2019-08-29
ODP
AngularJs Crash Course
PDF
Workshop 13: AngularJS Parte II
PDF
Vitaliy Makogon: Migration to ivy. Angular component libraries with IVY support.
PPTX
Angularjs Basics
PPTX
TRAINING pptt efwoiefo weoifjoiewjfoifjow.pptx
PDF
Top 7 Angular Best Practices to Organize Your Angular App
PPTX
Angular workshop - Full Development Guide
Commit University - Exploring Angular 2
Angular js
Developing maintainable Cordova applications
devjam2018 - angular 5 performance
Angular performance slides
From User Action to Framework Reaction
Angular Js Basics
Declarative presentations UIKonf
Taming event-driven software via formal verification
From User Action to Framework Reaction
Angular2 with type script
Angular Optimization Web Performance Meetup
Angular meetup 2 2019-08-29
AngularJs Crash Course
Workshop 13: AngularJS Parte II
Vitaliy Makogon: Migration to ivy. Angular component libraries with IVY support.
Angularjs Basics
TRAINING pptt efwoiefo weoifjoiewjfoifjow.pptx
Top 7 Angular Best Practices to Organize Your Angular App
Angular workshop - Full Development Guide
Ad

Recently uploaded (20)

PDF
How to Choose the Most Effective Social Media Agency in Bangalore.pdf
PDF
Become an Agentblazer Champion Challenge
PPTX
Materi-Enum-and-Record-Data-Type (1).pptx
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
The Role of Automation and AI in EHS Management for Data Centers.pdf
PDF
Jenkins: An open-source automation server powering CI/CD Automation
PDF
Become an Agentblazer Champion Challenge Kickoff
PPT
Introduction Database Management System for Course Database
PDF
Microsoft Teams Essentials; The pricing and the versions_PDF.pdf
PPTX
AIRLINE PRICE API | FLIGHT API COST |
PPTX
Online Work Permit System for Fast Permit Processing
PDF
Convert Thunderbird to Outlook into bulk
PPTX
CRUISE TICKETING SYSTEM | CRUISE RESERVATION SOFTWARE
PPTX
10 Hidden App Development Costs That Can Sink Your Startup.pptx
PPTX
Hire Expert Blazor Developers | Scalable Solutions by OnestopDA
PDF
The Future of Smart Factories Why Embedded Analytics Leads the Way
DOCX
The Five Best AI Cover Tools in 2025.docx
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
Comprehensive Salesforce Implementation Services.pdf
PPTX
Save Business Costs with CRM Software for Insurance Agents
How to Choose the Most Effective Social Media Agency in Bangalore.pdf
Become an Agentblazer Champion Challenge
Materi-Enum-and-Record-Data-Type (1).pptx
How Creative Agencies Leverage Project Management Software.pdf
The Role of Automation and AI in EHS Management for Data Centers.pdf
Jenkins: An open-source automation server powering CI/CD Automation
Become an Agentblazer Champion Challenge Kickoff
Introduction Database Management System for Course Database
Microsoft Teams Essentials; The pricing and the versions_PDF.pdf
AIRLINE PRICE API | FLIGHT API COST |
Online Work Permit System for Fast Permit Processing
Convert Thunderbird to Outlook into bulk
CRUISE TICKETING SYSTEM | CRUISE RESERVATION SOFTWARE
10 Hidden App Development Costs That Can Sink Your Startup.pptx
Hire Expert Blazor Developers | Scalable Solutions by OnestopDA
The Future of Smart Factories Why Embedded Analytics Leads the Way
The Five Best AI Cover Tools in 2025.docx
How to Migrate SBCGlobal Email to Yahoo Easily
Comprehensive Salesforce Implementation Services.pdf
Save Business Costs with CRM Software for Insurance Agents

Angular 16 – the rise of Signals

  • 1. Angular Meetup Angular v16, May 2023 The Rise of Angular Signals
  • 2. Angular 16 "The biggest release since the initial rollout of Angular"
  • 3. Yaron Biton Misterbit CTO Who am I? Coding Academy Chief Instructor
  • 4. Angular 16 This release has a significant impact on any Angular team Let's deep dive into how to code modern Angular apps
  • 5. • Anything web, end to end projects • Tech companies and startups • Consulting to management and dev teams • Available on demand coders https://www.misterbit.co.il
  • 6. • Advanced web-techs training: Angular, React, Vue, Node, Modern architectures, etc. • Catchup workshops for managers and leaders • Coding Academy bootcamp • Advanced selective training - best practices • Hundreds of employed full-stack developers every year https://www.coding-academy.org
  • 7. Angular 16 features We can (finally) use self-enclosing-tags! Becomes: <super-duper-long-component-name [prop]="someVar"> </super-duper-long-component-name> <super-duper-long-component-name [prop]="someVar"/>
  • 8. Angular 16 features We (finally) have a way to require a component @Input:
  • 9. Angular 16 features – Rethinking NgModules Tooling for standalone components, directives and pipes // The schematics convert an existing project // remove unnecessary NgModules classes, // and change the bootstrap of the project to use standalone APIs: ng generate @angular/core:standalone // new projects as standalone from the start: ng new --standalone // Creates a simpler project without any NgModules and with // generators that produce standalone directives, components, and pipes // Generate a standalone component ng generate componenet --standalone cmpName
  • 10. Rethinking the CLI – (As most modern CLIs ) > ng serve // Vite development server! Angular CLI is now Vite!
  • 13. Preface • On February 2023, Angular's team introduced Signals to the framework with a simple pull request. • Since then, there have been a storm in the Angular community about its use and benefits • …and if it’s another rewrite of the framework
  • 14. • In a model-driven web application, one of the main jobs of the framework is synchronizing changes to the application's data model and the UI. • We refer to this mechanism as reactivity, and every modern web framework has its own reactivity system. Reactivity with Signals
  • 15. Welcome Signals • In modern Angular every piece of important data is wrapped and used as signals • So, signals become immediately the most basic and important feature in Angular
  • 16. Signals • Angular Signals have an initial value • When executed, they return the current value of the signal quantity_ = signal<number>(1) selectedCar_ = signal<Car>(null) userMsg_ = signal({ txt: '', type: '' }) cars_ = signal<Car[]>([]) <h5> Quantity: {{ quantity_() }} </h5> <user-msg [msg]="userMsg_()"></user-msg>
  • 17. Setting Signal value The signal function returns a WritableSignal<T> which allow modifying the value: // Replace the signal value const movies_ = signal<Movie[]>([]) movies_.set([{ name: 'Fight club' }]) // Derive a new value const someCount_ = signal<Number>(0) someCount_.update(n => n + 1) // Perform internal mutation of arrays and other objects movies_.mutate(list => { list.push({name: 'Aba Ganuv'}) })
  • 18. computed values computed() creates a memoizing signal, which calculates its value from some other signals const counter_ = signal(0) // Automatically updates when `counter` changes: const isEven_ = computed(() => counter_() % 2 === 0) The value of the computed signal is being recalculated whenever any of it's dependencies changes.
  • 19. computed() Signal • The computed() function returns a Signal and not a WritableSignal, which means it cannot be manually modified (with set, update, or mutate) • Instead, it is updated automatically whenever one of its dependent signals change. export function computed<T>( computation: () => T, equal: ValueEqualityFn<T> = defaultEquals): Signal<T> const moviesCount_ = computed(() => movies_().length)
  • 20. side effect() effect() schedules and runs a side-effectful function Signal dependencies of this function are captured, and the side effect is re-executed whenever any of its dependencies produces a new value. const counter_ = signal(0) effect(() => console.log('The counter is:', counter_())) // The counter is: 0 counter_.set(1) // The counter is: 1 Effects do not execute synchronously with the set but are scheduled and resolved by the framework. The exact timing of effects is unspecified.
  • 21. effect() use cases Effects are useful in specific situations. Here are some examples: • Performing custom rendering to a <canvas> • Charting library, or other third party UI library • Keeping data in sync with window.localStorage
  • 22. Demo Time Before going deeper let's play with some sample code
  • 23. effect() registration • Registering a new effect with the effect() function requires an "injection context" (access to the inject function). • So we call either: • Create the effect within a constructor • Assign the effect to a field • Pass an Injector to effect via its options: @Component({...}) export class EffectiveCounterCmp { readonly count_ = signal(0) constructor(private injector: Injector) {} initializeLogging(): void { effect(() => { console.log(`The count is: ${this.count_()})`) }, {injector: this.injector}) } }
  • 24. effect() registration caveat Registering an effect in the wrong place, produces a weird message: ngOnInit() { effect(() => console.log(JSON.stringify(this.cars_()))) }
  • 25. Into Signals • Signal is a reactive value and is a producer that notify consumers(dependents) when it changes. • Any code that has registered an interest in the Signal’s value is tracked as dependent. • When the Signal’s value is modified, the Signal will notify all of its dependents, allowing them to react to the change in the Signal’s value.
  • 26. Adding dependents (consumer) to a Signal • When we use a signal in our template, this dependency is being added to that signal • We can add consumers by using effect and computed functions.
  • 27. Signal effect destroy • effects are automatically destroyed when their enclosing context is destroyed • The effect() function gets an onCleanup function and returns an EffectRef, that can be used for manually destroy • destroy(): 🧹 Shut down the effect, removing it from any upcoming scheduled executions. • The signature of the effect function: export function effect( effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef
  • 28. Signal equality functions • When creating a signal, we can optionally provide an equality function • It will be used to check whether the new value is actually different than the previous one import _ from 'lodash' const data_ = signal(['test'], {equal: _.isEqual}) // Using deep equality - signal won't trigger any updates data_.set(['test'])
  • 29. untracking reading without tracking dependencies // You can prevent a signal read from being tracked by calling its getter with untracked: effect(() => { console.log(`User set to `${ currentUser_() }` and the counter is ${untracked(counter_)}`) }) // Another example - invoke some external code which shouldn't be treated as a dependency: effect(() => { const user = currentUser_() untracked(() => { // If the `loggingService` reads signals, they won't be counted as // dependencies of this effect. this.loggingService.log(`User set to ${user}`) }) })
  • 30. Into the RFC Lets review some points from the official docs
  • 31. The downfall of Global, top-down change detection "Angular's default strategy is to run change detection over the entire component tree to make sure that the DOM reflects the most up-to-date model. Because Angular has no information about which parts of the application state have actually changed, it must check everything. In practice, however, only a fraction of the entire application state changes and only a handful of components need to be re-rendered."
  • 32. The downfall of onPush "While the OnPush strategy can reduce some of the performance cost, this strategy is (very) limited: change detection always starts from the root component Additionally - OnPush components prevent their descendants from being checked, descendent components that depend on global state are not updated."
  • 33. The downfall of the single traversal "Angular's change detection was designed to refresh application state once. The change detection process starts from the root of the component tree and walks all components down to the leaf nodes. However, many common web interaction patterns roll up descendant node states into ancestor nodes (e.g. form validity is computed as a function of descendant control states). This leads to the most "popular" error in Angular - ExpressionChangedAfterItHasBeenCheckedError."
  • 34. The downfall of Zone.js "Crucially, zone.js does not provide "fine-grained" information about changes in the model. Zone.js is only capable of notifying us when something might have happened in the application, and can give no information about what happened or what has changed."
  • 35. The downfall of Zone.js • "Large applications often grow to see zone.js become a source of performance issues and developer-facing complexity. • As the web platform continues to evolve, it also represents a rising maintenance cost for the Angular team."
  • 36. The downfall of Zone.js Zone Pollution https://angular.io/guide/change-detection-zone-pollution @Component(...) class AppComponent { constructor(private ngZone: NgZone) { } ngOnInit() { this.ngZone.runOutsideAngular(() => { // Sometimes we need to skip running change detection: Plotly.newPlot('chart', data) }) } }
  • 37. The downfall of Zone.js async – await?... WARNING: Zone.js does not support native async/await. These blocks are not intercepted by zone.js and will not trigger change detection. See: https://github.com/angular/zone.js/pull/1140 for more information.
  • 39. RxJS remains in HttpClient The good side: Makes it easy to design asycn patterns such as keeping one request per input trigger (switchMap) or piping into other Rxjs operators Which http calls should be made in parallel (`mergeMap`), cancel the one ongoing and move to the new one (`switchMap`), make one after another has finished (`concatMap`), etc etc. The bad side: Setting up streams for one-time ajax calls
  • 40. RxJS Interop - takeUntilDestroy It is very common to tie the lifecycle of an Observable to a particular component’s lifecycle // Here is a common Angular pattern: destroyed$ = new ReplaySubject<void>(1) data$ = http.get('...').pipe(takeUntil(this.destroyed$)) ngOnDestroy() { this.destroyed$.next() } // Now, we can: data$ = http.get('…').pipe(takeUntilDestroyed())
  • 41. RxJS Interop Convert a signal to observable: import { toObservable, signal } from '@angular/core/rxjs-interop' @Component({ ...}) export class App { count_ = signal(0) count$ = toObservable(this.count_) ngOnInit() { this.count$.subscribe(() => ...) } }
  • 42. RxJS Interop Convert an observable to a signal: import { toSignal } from '@angular/core/rxjs-interop' @Component({ template: ` <li *ngFor="let row of data_()"> {{ row }} </li> ` }) export class App { dataService = inject(DataService) data_ = toSignal(this.dataService.data$, []) } Promise => Observable => Signal someone?
  • 43. Simple counter signal Here is a simple counter signal @Component({ template: ` <div>Count: {{ count_() }}</div> <div>Double: {{ doubleCount_() }}</div> ` }) export class App { count_ = signal(0) doubleCount_ = computed(() => this.count_() * 2) constructor() { setInterval(() => this.count_.set(this.count_() + 1), 1000) } }
  • 44. Simple RxJS counter import { BehaviorSubject, map, take } from 'rxjs' export class AppComponent { template: ` <div>Count: {{ count$ | async }}</div> <div>Double: {{ doubledCount$ | async }}</div> `, count$ = timer(0, 1000) doubleCount$ = this.count$.pipe(map((v) => v * 2)) } Here is a simple counter with RxJS:
  • 45. RxJS is hard for humans RxJS has a still learning curve, some developers would code it like that: import { BehaviorSubject, map, take } from 'rxjs' @Component({ selector: 'counter', template: ` <div>Count: {{ count$ | async }}</div> <div>Double: {{ doubleCount$ | async }}</div> ` }) export class AppComponent { count$ = new BehaviorSubject(0) doubleCount$ = this.count$.pipe(map((value) => value * 2)) constructor() { setInterval(() => { let currentCount = 0 this.count$.pipe(take(1)).subscribe((x) => (currentCount = x)) this.count$.next(currentCount + 1) }, 1000) } }
  • 46. The downfall of Rxjs • "Angular does not internally use RxJS to propagate state or drive rendering in any way. • Angular only uses RxJS as a convenient EventEmitter completely disconnected from the change detection and rendering system" • Amm.. what about async pipes?..
  • 47. The downfall of Rxjs RxJS is not glitch-free - It's easy to craft an example which shows this behavior: import { BehaviorSubject, combineLatest, map } from 'rxjs' const counter$ = new BehaviorSubject(0) const isEven$ = counter$.pipe(map((value) => value % 2 === 0)) const message$ = combineLatest( [counter$, isEven$], (counter, isEven) => `${counter} is ${isEven ? 'even' : 'odd'}` ) message$.subscribe(console.log) counter$.next(1) // 0 is even // 1 is even ??? // 1 is odd In asynchronous RxJS code, glitches are not typically an issue because async operations naturally resolve at different times. Most template operations, however, are synchronous, and inconsistent intermediate results can have drastic consequences for the UI. For example, an NgIf may become true before the data is actually ready
  • 48. The downfall of Rxjs "Signals replace the currently used BehaviorSubject With Signals, Subscriptions get created and destroyed automatically under the hood. More or less what happens using the async pipe in RxJS. However, unlike Observables, Signals don’t require a Subscription to be used outside the template."
  • 49. The downfall of Rxjs "Using the async pipe several times creates separate Subscriptions and separate HTTP requests. Developers need to be conscious about using a shareReplay or avoid multiple subscribers to avoid multiple calls and side effects.
  • 50. The downfall of Immutability "Signals work with both, we don't want to "pick sides" but rather let developers choose the approach that works best for their teams and use-cases.
  • 51. Signal-based Components @Component({ signals: true, selector: 'user-profile', template: ` <p>Name: {{ firstName_() }} {{ lastName_() }}</p> <p>Account suspended: {{ suspended_() }}</p> `, }) export class UserProfile { firstName_ = input<string>() // Signal<string|undefined> lastName_ = input('Smith') // Signal<string> suspended_ = input<boolean>(false, { alias: 'disabled', })} Upcoming
  • 52. Model inputs and two-ways-data-binding @Component({ signals: true, selector: 'some-checkbox', template: ` <p>Checked: {{ checked() }}</p> <button (click)="toggle()">Toggle</button> `, }) export class SomeCheckbox { // Create a *writable* signal. checked_ = model(false) toggle() { checked_.update(c => !c) } }
  • 53. Model inputs and two-ways-data-binding @Component({ signals: true, selector: 'some-page', template: ` <!-- Note that the getter is *not* called here, the raw signal is passed --> <some-checkbox [(checked)]="isAdmin_" /> `, }) export class SomePage { isAdmin_ = signal(false) }
  • 54. Outputs in signal-based components @Component({ signals: true, selector: 'simple-counter', template: ` <button (click)="save()">Save</button> <button (click)="reset()">Reset</button> `, }) export class SimpleCounter { saved = output<number>() // EventEmitter<number> cleared = output<number>({alias: 'reset'}) save() { this.saved.emit(123) } reset() { this.cleared.emit(456) } }
  • 55. Signal based queries @Component({ signals: true, selector: 'form-field', template: ` <field-icon *ngFor="let icon of icons()"> {{ icon }} </field-icon> <div class="focus-outline"> <input #field> </div> ` }) export class FormField { icons = viewChildren(FieldIcon) // Signal<FieldIcon[]> input = viewChild<ElementRef>('field') // Signal<ElementRef> someEventHandler() { this.input().nativeElement.focus() } }
  • 56. Life cycle hooks • Zone-based components support eight different lifecycle methods. • Many of these methods are tightly coupled to the current change detection model, and don't make sense for signal- based components. • Signal-based components will retain the following lifecycle methods: • ngOnInit • ngOnDestroy
  • 57. Life cycle hooks To supplement signal components, three new application-level lifecycle hooks: function afterNextRender(fn: () => void): void function afterRender(fn: () => void): {destroy(): void} function afterRenderEffect(fn: () => void): {destroy(): void} * Why not ChangeDetectionStrategy.Signals?..
  • 58. Life cycle hooks Here is an example: @Component({ template: ` <p #p>{{ longText_() }}</p> `, }) export class AfterRenderCmp { constructor() { afterNextRender(() => { console.log('text height: ' + p_().nativeElement.scrollHeight) }) } p_ = viewQuery('p') }
  • 59. Is this another Angular rewrite? Hmmm…. yes The Angular team takes backwards compatibility seriously But many things change in the surface API Don’t rush: While many library developers will work on signal support we gonna dance the semver for some time Consider Microfrontends where applicable
  • 60. • Available on demand coders • Advanced web-techs training • Consulting and workshops for leaders Contact us: [email protected] https://www.misterbit.co.il https://www.coding-academy.org Know anyone that is a good fit? send'em our way!