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.
History
Angular 9
AOT by default
IVY support
.....
Angular 18
Zoneless
Defer block
Angular 14
Standalone Components`
.....
Angular 17
Control Flow
Angular 9
AOT by default
IVY support
.....
Angular 14
Standalone Components`
.....
Angular 17
Control Flow
Angular 18
Zoneless
Defer block
DevEx
Performance
UX
Code that is easier to understand and work with
DevEx
Performance
UX
Runtime
Build
DevEx
Performance
UX
How a user feels when interacting with a product
DevEx
Performance
UX
defer block
esBuild &
Vite
signals
control flow
Control Flow
if
if (this.isTruthy) {
// do something
} else if (this.isSomethingElseTruthy) {
// do something else
} else {
// do something else
}<ng-container *ngIf="isTruthy; else elseIfTemplate">
template of the if condition
</ng-container>
<ng-template #elseIfTemplate>
<ng-container *ngIf="isSomethingElseTruthy; else elseTemplate">
template of the else if condition
</ng-container>
</ng-template>
<ng-template #elseTemplate>
template of the else condition
</ng-template><ng-container *ngIf="isTruthy; else elseIfTemplate">
template of the if condition
</ng-container>
<ng-template #elseIfTemplate>
<ng-container *ngIf="isSomethingElseTruthy; else elseTemplate">
template of the else if condition
</ng-container>
</ng-template>
<ng-template #elseTemplate>
template of the else condition
</ng-template>if (this.isTruthy) {
// do something
} else if (this.isSomethingElseTruthy) {
// do something else
} else {
// do something else
}@if(isTruthy) {
template of the if condition
} @else if(isSomethingElseTruthy) {
template of the else if condition
} @else {
template of the else condition
}@if(isTruthy) {
template of the if condition
} @else if(isSomethingElseTruthy) {
template of the else if condition
} @else {
template of the else condition
}if (this.isTruthy) {
// do something
} else if (this.isSomethingElseTruthy) {
// do something else
} else {
// do something else
}@if(isTruthy) {
template of the if condition
} @else if(isSomethingElseTruthy) {
template of the else if condition
} @else {
template of the else condition
}<ng-container *ngIf="isTruthy; else elseIfTemplate">
template of the if condition
</ng-container>
<ng-template #elseIfTemplate>
<ng-container *ngIf="isSomethingElseTruthy; else elseTemplate">
template of the else if condition
</ng-container>
</ng-template>
<ng-template #elseTemplate>
template of the else condition
</ng-template>DevEx
@if(isTruthy) {
template of the if condition
}<ng-container *ngIf="isTruthy;">
template of the if condition
</ng-container>~70% Faster
for
<ng-container *ngIf="arrayOfItems.length; else emptyTemplate">
<ng-container *ngFor="let item of arrayOfItems">
<app-item [item]="item"></app-item>
</ng-container>
</ng-container>
<ng-template #emptyTemplate>
<p>No items found</p>
</ng-template>@for (item of arrayOfItems; track item) {
<app-item [item]="item"></app-item>
} @empty {
<p>No items found</p>
}@for (item of arrayOfItems; track item) {
<app-item [item]="item"></app-item>
} @empty {
<p>No items found</p>
}<ng-container *ngIf="arrayOfItems.length; else emptyTemplate">
<ng-container *ngFor="let item of arrayOfItems">
<app-item [item]="item"></app-item>
</ng-container>
</ng-container>
<ng-template #emptyTemplate>
<p>No items found</p>
</ng-template>@for (item of arrayOfItems; track item) {
<app-item [item]="item"></app-item>
} @empty {
<p>No items found</p>
}
90% Faster
switch (this.size) {
case 'small':
// do something
break;
case 'medium':
// do something
break;
case 'large':
// do something
break;
default:
// do something
break;
}<div [ngSwitch]="size">
<p *ngSwitchCase="'small'">You selected small</p>
<p *ngSwitchCase="'medium'">You selected medium</p>
<p *ngSwitchCase="'large'">You selected large</p>
<p *ngSwitchDefault>Invalid size</p>
</div><div [ngSwitch]="size">
<p *ngSwitchCase="'small'">You selected small</p>
<p *ngSwitchCase="'medium'">You selected medium</p>
<p *ngSwitchCase="'large'">You selected large</p>
<p *ngSwitchDefault>Invalid size</p>
</div>switch (this.size) {
case 'small':
// do something
break;
case 'medium':
// do something
break;
case 'large':
// do something
break;
default:
// do something
break;
}@switch (size) {
@case ('small') {
<p>You selected small</p>
}
@case ('medium') {
<p>You selected medium</p>
}
@case ('large') {
<p>You selected large</p>
}
@default {
<p>Invalid size</p>
}
}@switch (size) {
@case ('small') {
<p>You selected small</p>
}
@case ('medium') {
<p>You selected medium</p>
}
@case ('large') {
<p>You selected large</p>
}
@default {
<p>Invalid size</p>
}
}switch (this.size) {
case 'small':
// do something
break;
case 'medium':
// do something
break;
case 'large':
// do something
break;
default:
// do something
break;
}Performance By Default
Are you already there?
ng g @angular/core:control-flowDevEx
Performance
UX
Defer Block
export const routes: Routes = [
{
path: 'yellow',
loadComponent: () =>
import('./yellow.component').then((it) => it.YellowComponent),
},
{
path: 'cyan',
loadComponent: () =>
import('./cyan.component').then((it) => it.CyanComponent),
},
{
path: 'purple',
loadComponent: () =>
import('./purple.component').then((it) => it.PurpleComponent),
},
];chunk-yellow.js
chunk-cyan.js
chunk-purple.js
chunk-yellow.js
chunk-cyan.js
chunk-purple.js
chunk-yellow.js
chunk-cyan.js
chunk-purple.js
chunk-yellow.js
chunk-cyan.js
chunk-purple.js
User Flow
A
chunk-cyan-1.js
User Flow
Β
chunk-cyan-2.js
@defer {
<lazy-big-component />
}chunk-yellow.js
chunk-cyan-1.js
chunk-purple.js
chunk-cyan-2.js
defer
chunk-yellow.js
chunk-cyan-1.js
chunk-purple.js
chunk-cyan-2.js
defer
chunk-yellow.js
chunk-cyan-1.js
chunk-purple.js
chunk-cyan-2.js
defer
chunk-yellow.js
chunk-cyan-1.js
chunk-purple.js
chunk-cyan-2.js
defer
@defer {
<lazy-big-component />
} @loading {
<div> Loading... </div>
} @placeholder {
<div> The lazy big component will be placed here </div>
} @error {
<div> Oops, something went wrong! </div>
}@defer {
<lazy-big-component />
} @loading (after 1s; minimum 2s) {
<div> Loading... </div>
} @placeholder (minimum 2s) {
<div> The lazy big component will be placed here </div>
} @error {
<div> Oops, something went wrong! </div>
}50% Bundle Size
DevEx
Performance
UX
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);
});
}DevEx
Angular checks the entire component tree when the micro-task queue is empty
Default + Observable
OnPush + Observable
OnPush + Observable
Dirty
Dirty
Dirty
<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
DevEx
Performance
UX
esBuild & vite
Written in TypeScript
Parse the code with TS-Compiler
Build Optimizer
JS - Single Threaded
Written in Go
Compiled language
Optimized
Parallelization
Fast cold app start
Pre bundling
Caching
Incremental bundling
ng updatebrowser
browser-esbuild
Cold Build
Hot Rebuild
Production Build
88"
17"
97"
Cold Build
Hot Rebuild
Production Build
88"
17"
97"
21" (76%)
7" (58%)
27" (72%)
Thank you !!
/prodromouf
https://blog.profanis.me
@prodromouf
Code Shots With Profanis
Fanis Prodromou
/prodromouf
https://blog.profanis.me
@prodromouf
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)
}OLD
ng build -> webpack -> build optimization -> terser
ng serve -> vite (very fast cold start)
The initial bundle that is required to bootstrap the application is pre-bundled, so that we have a fast rendering.
Vite provides a lazy bundling. Fast page load, incremental bundling
OLD
ng build -> webpack -> build optimization -> terser
ng build -> esBuild (3x faster production builds)
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
effect(() => {
console.log(this.counter());
});
Log Context
Consumer
Consumer
<div>
{{ counter() }}
</div>Consumer
Template Context
Consumer
const evenOrOdd =
computed(() => counter() % 2 === 0 ? 'even' : 'odd');
Consumer & Producer
Producer
Consumer
Consumers
Producers
Consumers
Producers
Effect
Template
counter
Consumers
Producers
Effect
Template
counter
Template
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.