Slide 1

Slide 1 text

Confidence in templates and styles with Angular Ivy Angular Warsaw

Slide 2

Slide 2 text

Style binding evaluation

Slide 3

Slide 3 text

Style binding precedence rules for Angular Ivy 1. Template property bindings. 2. Template map bindings. 3. Static template class and style values. 4. Directive host property bindings. 5. Directive host map bindings. 6. Static directive host class and style bindings. 7. Component host property bindings. 8. Component host map bindings. 9. Static component host class and style bindings.

Slide 4

Slide 4 text

Template property bindings

Slide 5

Slide 5 text

Template map bindings

Slide 6

Slide 6 text

Static template class and style values

Slide 7

Slide 7 text

Directive host property bindings @Directive({ host: { '[style.background]': 'rebeccapurple', }, }) class StyleBackgroundDirective {}

Slide 8

Slide 8 text

Directive host map bindings @Directive({ host: { style: { background: 'rebeccapurple', }, }, }) class StyleMapDirective {}

Slide 9

Slide 9 text

Static directive host class and style bindings @Directive({ host: { style: 'background: rebeccapurple;', }, }) class StyleDirective {}

Slide 10

Slide 10 text

Component host property bindings @Component({ host: { '[style.background]': 'rebeccapurple', }, template: '', }) class StyleBackgroundComponent {}

Slide 11

Slide 11 text

Component host map bindings @Component({ host: { style: { background: 'rebeccapurple', }, }, template: '', }) class StyleMapComponent {}

Slide 12

Slide 12 text

Static component host class and style bindings @Component({ host: { style: 'background: rebeccapurple;', }, template: '', }) class StyleComponent {}

Slide 13

Slide 13 text

Caveat: NgClass and NgStyle Every time the view is updated, NgClass and NgStyle directives override all other bindings.
This element is red.

Slide 14

Slide 14 text

Caveat: Nullish values Style bindings override lower priorities when bound to null.
This element is transparent.

Slide 15

Slide 15 text

Caveat: Nullish values Style bindings skip to lower priorities when bound to undefined.
This element is purple.

Slide 16

Slide 16 text

Style binding ordering Style binding ordering does not affect style evaluation.
This element is blue.

Slide 17

Slide 17 text

Style binding ordering Style binding ordering does not affect style evaluation.
This element is blue.

Slide 18

Slide 18 text

Caveat: Style binding ordering Style binding ordering does not affect style evaluation except for bindings with the same precedence. Last one wins.
This element is blue.

Slide 19

Slide 19 text

Caveat: Style binding ordering Style binding ordering does not affect style evaluation except for bindings with the same precedence. Last one wins.
This element is red.

Slide 20

Slide 20 text

Style binding evaluation demo https://stackblitz.com/edit/angular-ivy-style-binding-precedence

Slide 21

Slide 21 text

Strict template type checking

Slide 22

Slide 22 text

Enabling strict template type checking // tsconfig.json (root) { "compilerOptions": { "strict": true, // 👈 // (...) }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, // 👈 "strictTemplates": true // 👈 } }

Slide 23

Slide 23 text

Configuring strict template type checking strictTemplates is shorthand for: * Also enabled by full template type checking mode. Additionally, strictInputAccessModifierscan be enabled. strictTemplates is shorthand for: • strictAttributeTypes • strictContextGenerics • strictDomEventTypes • strictDomLocalRefTypes • strictInputTypes • strictLiteralTypes * • strictNullInputTypes • strictOutputEventTypes • strictSafeNavigationTypes

Slide 24

Slide 24 text

strictAttributeTypes Angular Language Service Attribute-style property bindings are type-checked. ~~~~~ export class CounterComponent { @Input() count = 0; }

Slide 25

Slide 25 text

strictAttributeTypes Angular Compiler Attribute-style property bindings are type-checked. Error: projects/strict-app/src/app/app.component.html:1:14 - error TS2322: Type 'string' is not assignable to type 'number'. 1 ~~~~~ projects/strict-app/src/app/app.component.ts:7:16 7 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.

Slide 26

Slide 26 text

strictContextGenerics Angular Language Service Context component type parameters are type-checked. export class UserListComponent { @Input() users: readonly TUser[] = []; } export interface User { readonly address: Address; readonly name: string; }

Slide 27

Slide 27 text

strictContextGenerics Angular Language Service Context component type parameters are type-checked. {{ user.name }} ~~ export class UserListComponent { @Input() users: readonly TUser[] = []; } export interface User { readonly address: Address; readonly name: string; }

Slide 28

Slide 28 text

strictContextGenerics Angular Compiler Context component type parameters are type-checked. Error: projects/shared/src/lib/user-list.component.html:1:57 - error TS2339: Property 'id' does not exist on type 'TUser'. 1 ~~ projects/shared/src/lib/user-list.component.ts:10:16 10 templateUrl: './user-list.component.html', ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserListComponent.

Slide 29

Slide 29 text

strictDomEventTypes Angular Language Service The $event object is type-checked for native DOM event bindings. {{ text }} ~~~~~~ export class ButtonComponent { onClick(event: InputEvent): void { this.appClick.emit(); } }

Slide 30

Slide 30 text

strictDomEventTypes Angular Compiler The $event object is type-checked for native DOM event bindings. Error: projects/shared/src/lib/button.component.html:1:26 - error TS2345: Argument of type 'MouseEvent' is not assignable to parameter of type 'InputEvent'. Type 'MouseEvent' is missing the following properties from type 'InputEvent': data, inputType, isComposing 1 ~~~~~~ projects/shared/src/lib/button.component.ts:7:16 7 templateUrl: './button.component.html', ~~~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component ButtonComponent.

Slide 31

Slide 31 text

strictDomLocalRefTypes Angular Language Service Template variables for DOM elements are type-checked. ~~~~

Slide 32

Slide 32 text

strictDomLocalRefTypes Angular Compiler Template variables for DOM elements are type-checked. Error: projects/shared/src/lib/message-form.component.html:1:52 - error TS2339: Property 'rows' does not exist on type 'HTMLInputElement'. 1 ~~~~ projects/shared/src/lib/message-form.component.ts:7:16 7 templateUrl: './message-form.component.html', ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component MessageFormComponent.

Slide 33

Slide 33 text

strictInputAccessModifiers * Angular Language Service Prevent private, protected, and readonly input properties from being bound. ~~~~~ export class ReadonlyCounterComponent { @Input() readonly count = 0; } * Not enabled by the strictTemplates setting.

Slide 34

Slide 34 text

strictInputAccessModifiers * Angular Compiler Prevent private, protected, and readonly input properties from being bound. Error: projects/strict-app/src/app/app.component.html:2:24 - error TS2540: Cannot assign to 'count' because it is a read-only property. 2 ~~~~~ projects/strict-app/src/app/app.component.ts:7:16 7 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent. * Not enabled by the strictTemplates setting.

Slide 35

Slide 35 text

strictInputTypes Angular Language Service Input property bindings are type-checked. ~~~~~~~ export class MessageFormComponent { @Input() message = ''; }

Slide 36

Slide 36 text

strictInputTypes Angular Compiler Input property bindings are type-checked. Error: projects/strict-app/src/app/app.component.html:4:20 - error TS2322: Type 'number' is not assignable to type 'string'. 4 ~~~~~~~ projects/strict-app/src/app/app.component.ts:7:16 7 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.

Slide 37

Slide 37 text

strictLiteralTypes * Angular Language Service Array and object literals in component templates are type-checked. ~~ export class UserComponent { @Input() user: User | null = null; } * Also enabled by full template type checking mode.

Slide 38

Slide 38 text

strictLiteralTypes * Angular Language Service Array and object literals in component templates are type-checked. ~~ export interface Address { readonly city: string; } export interface User { readonly address: Address; readonly name: string; } * Also enabled by full template type checking mode.

Slide 39

Slide 39 text

strictLiteralTypes * Angular Compiler Array and object literals in component templates are type-checked. Error: projects/strict-app/src/app/app.component.html:4:30 - error TS2741: Property 'city' is missing in type '{}' but required in type 'Address'. 4 ~~ projects/strict-app/src/app/app.component.ts:7:16 7 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent. * Also enabled by full template type checking mode.

Slide 40

Slide 40 text

strictNullInputTypes Angular Language Service Input property bindings are strictly type-checked for nullishness. Similar to TypeScript’s strictNullCheck. ~~~~~ export class AppComponent { users$: Observable = of([ { address: { city: 'Montevideo' }, name: 'Nacho' }, { address: { city: 'Pune' }, name: 'Santosh' }, { address: { city: 'Hamburg' }, name: 'Serkan' }, ]); }

Slide 41

Slide 41 text

strictNullInputTypes Angular Compiler Input property bindings are strictly type-checked for nullishness. Similar to TypeScript’s strictNullCheck. Error: projects/strict-app/src/app/app.component.html:3:17 - error TS2322: Type 'readonly User[] | null' is not assignable to type 'readonly User[]'. Type 'null' is not assignable to type 'readonly User[]'. 3 ~~~~~ projects/strict-app/src/app/app.component.ts:9:16 9 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.

Slide 42

Slide 42 text

strictOutputEventTypes Angular Language Service The $event value is type-checked for custom event and animation bindings. ~~~~~~ export class AppComponent { #count: string = '0'; onCountChange(count: string): void { this.#count = count; } }

Slide 43

Slide 43 text

strictOutputEventTypes Angular Compiler The $event value is type-checked for custom event and animation bindings. Error: projects/strict-app/src/app/app.component.html:4:32 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. 4 (countChange)="onCountChange($event)" ~~~~~~ projects/strict-app/src/app/app.component.ts:9:16 9 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.

Slide 44

Slide 44 text

strictSafeNavigationTypes Angular Language Service Safe navigation operations in component templates are type-checked.

Name: {{ trim(user?.name) }}

~~~~~~~~~~ export class UserComponent { @Input() user: User | null = null; trim(text: string): string { return text.trim(); } }

Slide 45

Slide 45 text

strictSafeNavigationTypes Angular Compiler Safe navigation operations in component templates are type-checked. Error: projects/shared/src/lib/user.component.html:2:18 - error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'. 2

Name: {{ trim(user?.name) }}

~~~~~~~~~~ projects/shared/src/lib/user.component.ts:10:16 10 templateUrl: './user.component.html', ~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserComponent.

Slide 46

Slide 46 text

AsyncPipe type checking The strict check of null for property bindings is important for property bindings using the AsyncPipe as it initially emits a null. Input properties being bound using AsyncPipe must either have a type that includes null or use a concept known as input setter type hints.

Slide 47

Slide 47 text

Input setter type hints Widen accepted types for input property setter only. ~~~~ export class ButtonComponent { #text = 'Push me'; @Input() get text(): string { return this.#text; } set text(value: string) { this.#text = value; } }

Slide 48

Slide 48 text

Input setter type hints Widen accepted types for input property setter only. export class ButtonComponent { static ngAcceptInputType_text: string | null; // 👈 #text = 'Push me'; @Input() get text(): string { return this.#text; } set text(value: string) { this.#text = value === null ? 'Push me' : value; // 👈 } }

Slide 49

Slide 49 text

Configuring the Angular Language Service The Angular Language Service extension for Visual Studio Code only considers the tsconfig.json configuration in the workspace root folder.

Slide 50

Slide 50 text

Enabling strict template type checking for Angular libraries // tsconfig.lib.json { "extends": "../../tsconfig.json", "compilerOptions": { "strict": true, // 👈 // (...) }, "angularCompilerOptions": { "strictInjectionParameters": true, "strictInputAccessModifiers": true, // 👈 "strictTemplates": true // 👈 }, "exclude": ["src/test.ts", "**/*.spec.ts"] }

Slide 51

Slide 51 text

Example repository github.com/LayZeeDK/ngx-template-type-checking

Slide 52

Slide 52 text

Conclusion

Slide 53

Slide 53 text

Style binding precedence rules 1. Template bindings 2. Directive host bindings 3. Component host bindings Style binding ordering does not affect style evaluation.

Slide 54

Slide 54 text

Style bindings caveats • NgClass and NgStyle override other bindings • Nullish values affect style evaluation • Last style binding of same precedence wins

Slide 55

Slide 55 text

Strict template type checking • Enabling strict template type checking for Angular applications • Enabling strict template type checking for Angular libraries • Configuring strict template type checking • Configuring the Angular Language Service • Inline Angular Language Service type checks • Angular compiler type checks

Slide 56

Slide 56 text

Strict template type checks • Attribute-style property bindings • Input property bindings, including nullishness • Context component type parameters • Array and object literals in templates • Safe navigation template operations • $event value for custom events and animation bindings • $event object for native DOM events • Template variables for DOM elements

Slide 57

Slide 57 text

Access modifier template checks strictInputAccessModifiers prevents private, protected, and readonly input properties from being bound. Consider runtime private members (#-prefixed members) instead of private properties and methods. Consider getters with a private backing field instead of readonly members.

Slide 58

Slide 58 text

Strict template type checks with AsyncPipe strictNullInputTypes is like strictNullChecks but for input property bindings. AsyncPipe initially emits a null value. We can address this issue by either: • Adding null to our input property types • Accepting null through input setter type hints

Slide 59

Slide 59 text

Accelerating Angular Development with Ivy Available at Packt and Amazon. • 3 parts • 12 chapters • 200 pages • 12 feature apps • 1 real world app

Slide 60

Slide 60 text

Get in touch 👋 Lars Gyrup Brink Nielsen 🐦 @LayZeeDK

Slide 61

Slide 61 text

No content