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-flow

DevEx

Performance

UX

Defer Block

Declarative

Lazy Loading

Declarative

Lazy Loading

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 update

browser

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

  • Google Developer Expert for Angular
  • Senior Angular Developer  @ ASI
  • Co-organizer of Angular Athens Meetup
  • Angular Content Creator

Fanis Prodromou

/prodromouf

https://blog.profanis.me

@prodromouf

Angular's Renaissance: Exploring the Latest Gems

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)

What 

& How

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

Angular's Renaissance: Exploring the Latest Gems

By Fanis Prodromou

Angular's Renaissance: Exploring the Latest Gems

  • 207