Fanis Prodromou
I am a Senior Software Engineer with a passion for Front End development with Angular. I have developed vast experience in code quality, application architecture, and application performance.
Fanis Prodromou
/prodromouf
https://blog.profanis.me
@prodromouf
- What and Why
- Under the hood
- Signal APIs
- httpResource
- Q & A
- smart variables that notify anyone who's interested when their value changes.
What are Signals?
- Traditional change detection checks everything. Signals allow tracks specific values
- Traditional Change Detection can be slow on complex applications
Why Signals?
- zone.js is great but triggers the CD multiple times
Zone.js
User Interaction
dom event (click)
zone.js
(Angular Zone)
Change Detection
UI Update
Angular checks the entire component tree when the micro-task queue is empty
Default + Observable
OnPush + Observable
OnPush + Observable
Dirty
Dirty
Dirty
Consumers
Producers
Consumers
Producers
Observers
Subject
Consumers
Producers
Effect
Template
counter
Consumers
Producers
Effect
Template
counter
counter = signal<number>(0);
Producer
counter = signal<number>(0);
Returns a WritableSignal
Producer
counter = signal<number>(0);
Define the type
Producer
counter = signal<number>(0);
Default value
Producer
counter = signal<number>(0);
counter.set(1);
counter.update((existingValue) => existingValue + 1);Producer
<div>
{{ counter() }}
</div>Consumer
Template Context
Consumer
effect(() => {
console.log(this.counter());
});
Context
Consumer
Consumer
const evenOrOdd =
computed(() => counter() % 2 === 0 ? 'even' : 'odd');
Consumer & Producer
Producer
Consumer
<div> {{ counter() }} </div>const evenOrOdd =
computed(() => counter() % 2 === 0 ? 'even' : 'odd');const counter = signal(0);const counter = signal(0);<div> {{ counter() }} </div><div> {{ counter() }} </div><div> {{ counter() }} </div><div> {{ counter() }} </div>const evenOrOdd =
computed(() => counter() % 2 === 0 ? 'even' : 'odd');<div> {{ counter() }} </div>const evenOrOdd =
computed(() => counter() % 2 === 0 ? 'even' : 'odd');<div> {{ counter() }} </div>const evenOrOdd =
computed(() => counter() % 2 === 0 ? 'even' : 'odd');"Since Angular knows how the data flows, can have a more fine-grained change detection"
Signals
Signals
Traversal
Traversal
Traversal
const isValid = signal(true);
const username = signal('profanis');
effect(() => {
if (isValid() === true) {
console.log(username());
}
});
// Update signal values
isValid.set(false);
username.set('profanis2');effect(() => {
if (isValid() === true) {
console.log(username());
}
});
// Update signal values
isValid.set(false);
username.set('profanis2');Consumers
Producers
isValid
effect
username
effect
Push (notification)
effect(() => {
if (isValid() === true) {
console.log(username());
}
});
// Update signal values
isValid.set(false);
username.set('profanis2');Consumers
Producers
isValid
effect
username
effect
Push (notification)
effect(() => {
if (isValid() === true) {
console.log(username());
}
});
// Update signal values
isValid.set(false);
username.set('profanis2');Consumers
Producers
isValid
effect
username
effect
Pull (value)
effect(() => {
console.log(`${isValid()} - ${username()}`);
});
isValid.set(false);
username.set('profanis2');Consumers
Producers
isValid
effect
username
effect
Push (notification)
Consumers
Producers
isValid
effect
username
effect
Push (notification)
effect(() => {
console.log(`${isValid()} - ${username()}`);
});
isValid.set(false);
username.set('profanis2');Consumers
Producers
isValid
effect
username
effect
Pull (value)
Pull (value)
effect(() => {
console.log(`${isValid()} - ${username()}`);
});
isValid.set(false);
username.set('profanis2');effect
isValid
username
Signal APIs
signal
inputs
new output
model input
signal queries
signal
inputs
model input
signal queries
new output
model input
signal queries
new output
signal
inputs
model input
signal queries
new output
signal
inputs
model input
signal queries
new output
signal
inputs
signal
inputs
@Component({...})
export class MyComponent {
@Input() isChecked = false;
}@Component({...})
export class MyComponent {
isChecked = input(false);
}read-only signal
export interface UserModel {
name: string;
age: number;
/*Social*/
address: string;
twitter: string;
linkedin: string;
github: string;
instagram: string;
facebook: string;
website: string;
email: string;
}@Component({...})
export class MyComponent implements OnChanges {
@Input({ required: true }) user!: UserModel;
userSocials: string[] = [];
ngOnChanges(changes: SimpleChanges): void {
if (changes.user) {
const { name, age, ...userSocials } = this.user;
this.userSocials = Object.values(userSocials);
}
}
}@Component({...})
export class MyComponent {
user = input.required<UserModel>();
userSocials = computed(() => {
const { name, age, ...userSocials } = this.user();
return Object.values(userSocials);
});
}model input
@Component({...})
export class ChildComponent {
@Input({ required: true }) name!: string;
@Output() nameChange = new EventEmitter<string>();
}@Component({
selector: 'app-parent',
standalone: true,
imports: [ChildComponent],
template: `
<app-child [name]="username"
(nameChange)="changeHandler($event)" />
`,
})
export class ParentComponent {
username = 'profanis';
}@Component({
selector: 'app-parent',
standalone: true,
imports: [ChildComponent],
template: `
<app-child [name]="username"
(nameChange)="changeHandler($event)" />
`,
})
export class ParentComponent {
username = 'profanis';
}@Component({
selector: 'app-parent',
standalone: true,
imports: [ChildComponent],
template: ` <app-child [(name)]="username" /> `,
})
export class ParentComponent {
username = 'profanis';
}@Component({...})
export class ChildComponent {
// @Input({ required: true }) name!: string;
// @Output() nameChange = new EventEmitter<string>();
name = model<string>();
}@Component({...})
export class ChildComponent {
// @Input({ required: true }) name!: string;
// @Output() nameChange = new EventEmitter<string>();
name = model.required<string>();
}@Component({...})
export class ChildComponent {
name = model<string>(); // writable signal
addTitle() {
this.name.update((name) => `Mr. ${name}`);
}
}@Component({...})
export class ChildComponent {
name = model<string>(); // writable signal
titleExists = computed(() => this.name().startsWith('Mr.'));
}@Component({
selector: 'app-parent',
standalone: true,
imports: [ChildComponent],
template: ` <app-child [(name)]="username" /> `,
})
export class ParentComponent {
username = 'profanis';
}new output
@Component({...})
export class ChildComponent {
name = input.required<string>();
@Output() nameChange = new EventEmitter<string>();
}@Component({...})
export class ChildComponent {
name = input.required<string>();
nameChange = output<string>()
}@Component({...})
export class ChildComponent {
@Output() formIsValid = this.form.statusChanges.pipe(
map((status) => status === 'VALID'),
);
}import { outputFromObservable } from '@angular/core/rxjs-interop';
@Component({...})
export class ChildComponent {
formIsValid = outputFromObservable(
this.form.statusChanges.pipe(
map((status) => status === 'VALID'))
);
}Optional RxJS
import { outputToObservable } from '@angular/core/rxjs-interop';
@Component({...})
export class ChildComponent {
name = input.required<string>();
nameChange = output<string>()
nameChange$ = outputToObservable(nameChange)
}httpResource
httpResource makes a reactive HTTP request and exposes the request status and response value
httpResource(
?,
?
)httpResource(
string | object | function ,
?
)httpResource(
string | object | function ,
options
)// String
httpResource(`https://api.com/${signalValue()}`)// Object
httpResource(
{
url: `https://api.com/${signalValue()}`,
method: 'GET',
params: { type: `${queryParamSignalValue()}` }
}
)// Function
httpResource(() => `https://api.com/${signalValue()}`)// Function
httpResource(() =>
signalValue() ?
`https://api.com/${signalValue()}` :
undefined
)// String with Options
httpResource(`https://api.com/${signalValue()}`, {
defaultValue: {},
parse: (response) => zodSchema.parse(response),
})// String with Options
resource = httpResource(`https://api.com/${signalValue()}`, {
defaultValue: {},
parse: (response) => zodSchema.parse(response),
})@if (resource.isLoading()) {
<div>Loading...</div>
}
@if (resource.error()) {
<div>Oops...</div>
}
@if (resource.value()) {
<div>{{ optionsResource.value() }}</div>
}// String with Options
resource = httpResource(`https://api.com/${signalValue()}`, {
defaultValue: {},
parse: (response) => zodSchema.parse(response),
})derivedState = computed(
() => resource.value().map(it => it.title)
)Thank you !!
/prodromouf
https://blog.profanis.me
@prodromouf
Code Shots With Profanis
Consumers
Producers
Effect
Template
counter
Template
Effect
counter.set(1);
Produces new value
Consumers
Producers
Effect
Template
counter.set( )
Template
Effect
1
Consumers
Producers
Effect
Template
counter.set(1)
Template
Effect
1
Consumers
Producers
Effect
Template
counter.set(1)
Template
Effect
1
Consumers
Producers
Effect
Template
Template
Effect
1
counter.set(1)
1
Consumers
Producers
Effect
Template
Template
Effect
1
counter.set(1)
1
Consumers
Producers
Effect
Template
Template
Effect
1
counter.set(1)
1
Consumers
Producers
Effect
Template
Template
Effect
counter.set(1)
Counter
Counter
Consumers
Producers
Effect
Template
Template
Effect
counter.set(1)
Counter
Counter
Edge
ref_con
ref_prod
Consumers
Producers
Effect
Template
Template
Effect
counter.set(1)
Counter
Counter
Edge
ref_con
ref_prod
Consumers
Producers
Effect
Template
Template
Effect
counter.set(1)
Counter
Counter
Edge
ref_con
ref_prod
Consumers
Producers
Effect
Template
Template
Effect
counter.set(1)
Counter
Counter
Edge
ref_con
ref_prod
Consumers
Producers
Effect
Template
Template
Effect
counter.set(1)
Counter
Counter
Edge
ref_con
ref_prod
Consumers
Producers
counter
const counter = signal(0);
Consumers
Producers
counter
const counter = signal(0);
effect(() => console.log(counter());
Effect
Consumers
Producers
counter
const counter = signal(0);
effect(() => console.log(counter());
Counter
Effect
Consumers
Producers
counter
const counter = signal(0);
effect(() => console.log(counter());
Counter
Effect
Effect
Consumers
Producers
counter.set( )
const counter = signal(0);
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
1
Consumers
Producers
counter
const counter = signal(0);
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
1
Consumers
Producers
counter
const counter = signal(0);
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
1
Consumers
Producers
counter
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
computed
Consumers
Producers
counter
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter.set( )
1
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
1
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter());
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
1
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
computed
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
computed
Effect
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
computed
Effect
Consumers
Producers
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));
counter.set(1);
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
computed
Effect
Counter
Effect
Effect
Computed
Counter
Computed
counter
1
computed
Effect
console.log("1 is even")
console.log("1 is odd")console.log("1 is even")
console.log("1 is odd")Counter
Log
Log
Computed
Counter
Computed
counter
10
computed
Log
GLITCH
By Fanis Prodromou
I am a Senior Software Engineer with a passion for Front End development with Angular. I have developed vast experience in code quality, application architecture, and application performance.