Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Confidence in templates and styles with Angular Ivy

Confidence in templates and styles with Angular Ivy

We learn how to reason about style binding evaluation through style binding precedence rules with simple examples sprinkled with a few style binding caveats. We end this topic by reviewing a demo application to visualize Angular Ivy's style evaluation.

After that, we learn how to enable and configure strict template type checking. We see examples of all violations and how they appear inline in our code editor as well as during compilation. Finally, we learn how to use strict template type checks with the AsyncPipe.

Presented at:
- NG Meetup Yerevan, June 2021

Recording: https://youtu.be/XAEGU18i0oI?t=3463

Style binding evaluation demo: https://stackblitz.com/edit/angular-ivy-style-binding-precedence
Strict template type checking experiments: https://github.com/LayZeeDK/ngx-template-type-checking
Angular documentation: https://angular.io/guide/style-precedence

4f349dbe1d48627445735f7e2c818c97?s=128

Lars Gyrup Brink Nielsen

July 01, 2021
Tweet

Transcript

  1. Confidence in templates and styles with Angular Ivy NG Meetup

    Yerevan
  2. Style binding evaluation

  3. 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.
  4. Template property bindings <div [style.background]="'rebeccapurple'"> </div>

  5. Template map bindings <div [style]="{ background: 'rebeccapurple' }"> </div>

  6. Static template class and style values <div style="background: 'rebeccapurple';"></div>

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

    }) class StyleBackgroundDirective {}
  8. Directive host map bindings @Directive({ host: { style: { background:

    'rebeccapurple', }, }, }) class StyleMapDirective {}
  9. Static directive host class and style bindings @Directive({ host: {

    style: 'background: rebeccapurple;', }, }) class StyleDirective {}
  10. Component host property bindings @Component({ host: { '[style.background]': 'rebeccapurple', },

    template: '', }) class StyleBackgroundComponent {}
  11. Component host map bindings @Component({ host: { style: { background:

    'rebeccapurple', }, }, template: '', }) class StyleMapComponent {}
  12. Static component host class and style bindings @Component({ host: {

    style: 'background: rebeccapurple;', }, template: '', }) class StyleComponent {}
  13. Caveat: NgClass and NgStyle Every time the view is updated,

    NgClass and NgStyle directives override all other bindings. <div [ngStyle]="{ background: 'red' }" [style]="{ background: 'rebeccapurple' }"> This element is red. </div>
  14. Caveat: Nullish values Style bindings override lower priorities when bound

    to null. <div [style.background]="null" [style]="{ background: 'rebeccapurple' }"> This element is transparent. </div>
  15. Caveat: Nullish values Style bindings skip to lower priorities when

    bound to undefined. <div [style.background]="undefined" [style]="{ background: 'rebeccapurple' }"> This element is purple. </div>
  16. Style binding ordering Style binding ordering does not affect style

    evaluation. <div [style.background]="'blue'" [style]="{ background: 'rebeccapurple' }"> This element is blue. </div>
  17. Style binding ordering Style binding ordering does not affect style

    evaluation. <div [style]="{ background: 'rebeccapurple' }" [style.background]="'blue'"> This element is blue. </div>
  18. Caveat: Style binding ordering Style binding ordering does not affect

    style evaluation except for bindings with the same precedence. Last one wins. <div [style]="{ background: 'rebeccapurple' }" [style.background]="'blue'"> This element is blue. </div>
  19. Caveat: Style binding ordering Style binding ordering does not affect

    style evaluation except for bindings with the same precedence. Last one wins. <div [style]="{ background: 'rebeccapurple' }" [style.background]="'blue'" [style.background]="'red'"> This element is red. </div>
  20. Style binding evaluation demo https://stackblitz.com/edit/angular-ivy-style-binding-precedence

  21. Strict template type checking

  22. Enabling strict template type checking // tsconfig.json (root) { "compilerOptions":

    { "strict": true, // 👈 // (...) }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, // 👈 "strictTemplates": true // 👈 } }
  23. 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
  24. strictAttributeTypes Angular Language Service Attribute-style property bindings are type-checked. <app-counter

    count="1"></app-counter> ~~~~~ export class CounterComponent { @Input() count = 0; }
  25. 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 <app-counter count="1"></app-counter> ~~~~~ projects/strict-app/src/app/app.component.ts:7:16 7 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.
  26. strictContextGenerics Angular Language Service Context component type parameters are type-checked.

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

    <a *ngFor="let user of users" routerLink="/user/{{ user.id }}"> {{ user.name }} ~~ </a> export class UserListComponent<TUser extends User> { @Input() users: readonly TUser[] = []; } export interface User { readonly address: Address; readonly name: string; }
  28. 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 <a *ngFor="let user of users" routerLink="/user/{{ user.id }}"> ~~ projects/shared/src/lib/user-list.component.ts:10:16 10 templateUrl: './user-list.component.html', ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserListComponent.
  29. strictDomEventTypes Angular Language Service The $event object is type-checked for

    native DOM event bindings. <button (click)="onClick($event)"> {{ text }} ~~~~~~ </button> export class ButtonComponent { onClick(event: InputEvent): void { this.appClick.emit(); } }
  30. 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 <button (click)="onClick($event)"> ~~~~~~ projects/shared/src/lib/button.component.ts:7:16 7 templateUrl: './button.component.html', ~~~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component ButtonComponent.
  31. strictDomLocalRefTypes Angular Language Service Template variables for DOM elements are

    type-checked. <input #userControl /><button (click)="userControl.rows = 4"> ~~~~
  32. 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 <input #userControl /><button (click)="userControl.rows = 4"> ~~~~ projects/shared/src/lib/message-form.component.ts:7:16 7 templateUrl: './message-form.component.html', ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component MessageFormComponent.
  33. strictInputAccessModifiers * Angular Language Service Prevent private, protected, and readonly

    input properties from being bound. <app-readonly-counter [count]="1"></app-readonly-counter> ~~~~~ export class ReadonlyCounterComponent { @Input() readonly count = 0; } * Not enabled by the strictTemplates setting.
  34. 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 <app-readonly-counter [count]="1"></app-readonly-counter> ~~~~~ 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.
  35. strictInputTypes Angular Language Service Input property bindings are type-checked. <app-message-form

    [message]="2021"></app-message-form> ~~~~~~~ export class MessageFormComponent { @Input() message = ''; }
  36. 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 <app-message-form [message]="2021"></app-message-form> ~~~~~~~ projects/strict-app/src/app/app.component.ts:7:16 7 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.
  37. strictLiteralTypes * Angular Language Service Array and object literals in

    component templates are type-checked. <app-user [user]="{ address: {}, name: 'Lars' }"></app-user> ~~ export class UserComponent { @Input() user: User | null = null; } * Also enabled by full template type checking mode.
  38. strictLiteralTypes * Angular Language Service Array and object literals in

    component templates are type-checked. <app-user [user]="{ address: {}, name: 'Lars' }"></app-user> ~~ export interface Address { readonly city: string; } export interface User { readonly address: Address; readonly name: string; } * Also enabled by full template type checking mode.
  39. 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 <app-user [user]="{ address: {}, name: 'Lars' }"></app-user> ~~ 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.
  40. strictNullInputTypes Angular Language Service Input property bindings are strictly type-checked

    for nullishness. Similar to TypeScript’s strictNullCheck. <app-user-list [users]="users$ | async"></app-user-list> ~~~~~ export class AppComponent { users$: Observable<readonly User[]> = of([ { address: { city: 'Montevideo' }, name: 'Nacho' }, { address: { city: 'Pune' }, name: 'Santosh' }, { address: { city: 'Hamburg' }, name: 'Serkan' }, ]); }
  41. 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 <app-user-list [users]="users$ | async"></app-user-list> ~~~~~ projects/strict-app/src/app/app.component.ts:9:16 9 templateUrl: './app.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component AppComponent.
  42. strictOutputEventTypes Angular Language Service The $event value is type-checked for

    custom event and animation bindings. <app-readonly-counter (countChange)="onCountChange($event)"></app-readonly-counter> ~~~~~~ export class AppComponent { #count: string = '0'; onCountChange(count: string): void { this.count = count; } }
  43. 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.
  44. strictSafeNavigationTypes Angular Language Service Safe navigation operations in component templates

    are type-checked. <p>Name: {{ trim(user?.name) }}</p> ~~~~~~~~~~ export class UserComponent { @Input() user: User | null = null; trim(text: string): string { return text.trim(); } }
  45. 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 <p>Name: {{ trim(user?.name) }}</p> ~~~~~~~~~~ projects/shared/src/lib/user.component.ts:10:16 10 templateUrl: './user.component.html', ~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserComponent.
  46. 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.
  47. Input setter type hints Widen accepted types for input property

    setter only. <app-button [text]="buttonText$ | async"></app-button> ~~~~ export class ButtonComponent { #text = 'Push me'; @Input() get text(): string { return this.#text; } set text(value: string) { this.#text = value; } }
  48. Input setter type hints Widen accepted types for input property

    setter only. <app-button [text]="buttonText$ | async"></app-button> 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; // 👈 } }
  49. 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.
  50. 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"] }
  51. Example repository github.com/LayZeeDK/ngx-template-type-checking

  52. Conclusion

  53. Style binding precedence rules 1. Template bindings 2. Directive host

    bindings 3. Component host bindings Style binding ordering does not affect style evaluation.
  54. Style bindings caveats • NgClass and NgStyle override other bindings

    • Nullish values affect style evaluation • Last style binding of same precedence wins
  55. 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
  56. 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
  57. 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.
  58. 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
  59. Get in touch 👋 Lars Gyrup Brink Nielsen 🐦 @LayZeeDK

  60. None