Signals
ESBuild
ESLint
Standalone Components
NgImageDirective
No more ViewEngine
Strict (Reactive) Forms
EOL
- TSLint
- Karma
- Protractor
DevTools
Functional
Directive Composition API
Improved Router
Jest
NGCC
inject()
production by default
And that’s just Angular!
// Javascript private class member!
#myVar: string = "foo";
v12
Production builds by default
{
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
},
"configurations": {
"production": {
"fileReplacements": [...],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
},
}
}
}
Schematic
npx @angular/cli@12 update @angular/cli@12 --migrate-only production-by-default
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": { ...},
"configurations": {
"production": {
"budgets": [...],
"fileReplacements": [...],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
}
Serve
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "my-app:build:production"
},
"development": {
"browserTarget": "my-app:build:development"
}
},
"defaultConfiguration": "development"
},
EOL
v13
Test teardown by default
No more `entryComponents: []`
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
{ teardown: { destroyAfterEach: true } }
);
v14
inject()
vs constructor()
const ENVIRONMENT = new InjectionToken<Environment>('environment');
@Component({
// component metadata
})
export class AppComponent {
// Gebruik de nieuwe ES private class modifier `#` ipv `private`
// type "Environment" is inferred
#env = inject(ENVIRONMENT, { optional: true });
#myService = inject(MyService);
protected myUser$ = inject(UserService).getUser();
}
Angular Dependency Injection is heel erg krachtig en we zijn gewend om services en dergelijk the _injecteren_ via de constructor:
constructor(private myService: MyService, @Inject(ENVIRONMENT) private env: Environment){}
Dit zorgt zowel voor de initialisatie van de service als het gebruik van het provided `ENVIRONMENT
` token.
Echter is dit nu o.a. door changes in de Typescript Decorator niet meer de juiste manier.
En is dit de aangeraden manier:
inject()
: Voordelen
--experimentalDecorators
are no longer needed. useDefineForClassFields
hoeft niet op false
Router
export const ROUTES: Routes = [
{ path: '', title: 'Ninja Squad | Home', component: HomeComponent },
{ path: 'trainings', title: 'Ninja Squad | Trainings', component: TrainingsComponent }
]
Page Title
CanMatch guard
Vervangt canLoad
(Deprecated)
path
canActive
& canLoad
te definierenconst routes: Routes = [
{
path: 'todos',
canMatch: [() => inject(FeatureFlagsService).hasPermission('todos-v2')],
loadComponent: () => import('./todos-page-v2/todos-page-v2.component')
.then(v => v.TodosPageV2Component)
},
{
path: 'todos',
loadComponent: () => import('./todos-page/todos-page.component')
.then(v => v.TodosPageComponent)
}];
Typed Reactive Forms
Protected class member
protected myValue: string = 'foo';
<h1> {{ myValue }} </h1>
Template
NgOptimizedImage
<img [ngSrc]="imageUrl" width="400" height="300" />
Angular DevTools
v15
Standalone Components
Functional Guards/Interceptors/Resolvers
Directive Composition API (Host Directives)
@Component({
selector: 'mat-menu',
hostDirectives: [
HasColor,
{
directive: CdkMenu,
inputs: ['cdkMenuDisabled: disabled'],
outputs: ['cdkMenuClosed: closed']
}
]
})
class MatMenu {}
Router
Lazy-load default exports
// feature.routes.ts
export default [
{
path: '',
component: FeaturePage,
},
{
path: 'persoon',
component: PersoonPage,
}
] satisfies Route[];
// main routes
export const MAIN_ROUTES: Rout[] = {
path: '',
loadChildren: () => import('./feature/feature.routes'),
},
}
ESBuild (Experimental)
"architect": {
"build": { /* Add the esbuild suffix */
"builder": "@angular-devkit/build-angular:browser-esbuild",
}
}
self-closing tags
<my-component [data]="myData" />
Removed
environment
ng generate environments
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
N.B.
- environment.ts
is nu de prod
!!
polyfills.ts
{
"polyfills": ["zone.js","zone.js/testing"]
}
v16
Signals (Preview)
@Component({
selector: 'my-app',
standalone: true,
template: `
{{ fullName() }} <button (click)="setName('John')">Click</button>
`,
})
export class App {
firstName = signal('Jane');
lastName = signal('Doe');
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
constructor() {
effect(() => console.log('Name changed:', this.fullName()));
}
setName(newName: string) {
this.firstName.set(newName);
}
}
Met RxJs interop
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$, []);
}
Router
Required Inputs
@Input({ required: true }) color: 'red' | 'blue';
Transform
provideRouter(routes, withComponentInputBinding())
<my-component disabled></my-component>
<my-component disabled="true"></my-component>
<!-- Before, only the following was properly working -->
<my-component [disabled]="true"></my-component>
@Input voor Route Params
import {Input as RouterInput } from '@angular/router';
export class FeaturePage extends OnInit {
@RouterInput() userId?: string;
ngOnInit(){
// We should check userId here => cannot use `required`
}
}
@Input({ transform: booleanAttribute }) disabled = false;
v17
New Logo and Site!
https://angular.dev
Control Flow
<div *ngIf="loggedIn; else anonymousUser">
The user is logged in
</div>
<ng-template #anonymousUser>
The user is not logged in
</ng-template>
@if (loggedIn) {
The user is logged in
} @else {
The user is not logged in
}
*ngIf
@if / @else
Control Flow
import { NgFor} from '@angular/common';
@Component({
standalone: true,
imports: [ NgFor ],
template: `
<ul>
<li *ngFor="let ingredient of ingredientList; trackBy: trackByIngredientFn">
{{ ingredient.quantity }} - {{ ingredient.name }}
</li>
</ul>
`
})
@Component({
standalone: true,
template: `
<ul>
@for (ingredient of ingredientList; track ingredient.name) {
<li>{{ ingredient.quantity }} - {{ ingredient.name }}</li>
}
@empty {
<li> There are no items.</li>
}
</ul>
`
})
*ngFor
@for / @empty
Control Flow
<ng-container [ngSwitch]="accessLevel">
<admin-dashboard *ngSwitchCase="admin"/>
<moderator-dashboard *ngSwitchCase="moderator"/>
<user-dashboard *ngSwitchDefault/>
</ng-container>
@switch (accessLevel) {
@case ('admin') { <admin-dashboard/> }
@case ('moderator') { <moderator-dashboard/> }
@default { <user-dashboard/> }
}
*ngSwitch
@switch
Control Flow
ng generate @angular/core:control-flow
Migrate!!
Deferrable Views
@defer (on viewport) {
<comment-list/>
} @loading {
Loading…
} @error {
Loading failed :(
} @placeholder {
<img src="comments-placeholder.png">
}
@defer
Deferrable Views
<button type="button" #searchButton>Search</button>
@defer (on interaction(searchButton)) {
<search-results />
} @loading {
<div> loading items</div>
}
Triggers
Signals
import { Input, numberAttribute, booleanAttribute } from '@angular/core';
@Input({ required: true }) bankName!: string;
@Input({ transform: booleanAttribute }) status: boolean;
// ROUTER PARAM!
@Input({ alias:'account-id', transform: numberAttribute }) id: number;
@Input()
input()
import { input, numberAttribute, booleanAttribute } from '@angular/core';
bankName = input.required<string>();
status = input(false, { transform: booleanAttribute });
// ROUTER PARAM!
id = input<number>({ alias:'account-id', transform: numberAttribute });
Signals
model() inputs
import {Component, model, input} from '@angular/core';
@Component({
selector: 'custom-checkbox',
template: '<div (click)="toggle()"> ... </div>',
})
export class CustomCheckbox {
checked = model(false);
disabled = input(false);
toggle() {
// While standard inputs are read-only, you can write directly to model inputs.
this.checked.set(!this.checked());
}
}
Model inputs are a special type of input that enable a component to propagate new values back to another component.
Signals
import {Component, model, input} from '@angular/core';
@Component({
selector: 'custom-checkbox',
template: '<div (click)="toggle()"> ... </div>',
})
export class CustomCheckbox {
checked = model(false);
disabled = input(false);
toggle() {
// While standard inputs are read-only, you can write directly to model inputs.
this.checked.set(!this.checked());
}
}
Two-way-binding