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 https://youtu.be/XAEGU18i0oI?t=3463
- Angular Community Meetup, September 2021
- ATP (private event), October 2021
- Angular Warsaw Online #34, October 2021 https://youtu.be/5pJd0apGHTk?t=2118

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

Accelerating Angular Development with Ivy
Book listing: https://www.packtpub.com/product/accelerating-angular-development-with-ivy/9781800205215
Companion GitHub repository: https://github.com/PacktPublishing/Accelerating-Angular-Development-with-Ivy

Lars Gyrup Brink Nielsen

July 01, 2021
Tweet

More Decks by Lars Gyrup Brink Nielsen

Other Decks in Programming

Transcript

  1. Confidence in templates and
    styles with Angular Ivy
    Angular Warsaw

    View Slide

  2. Style binding evaluation

    View Slide

  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.

    View Slide

  4. Template property bindings


    View Slide

  5. Template map bindings


    View Slide

  6. Static template class and style values

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  13. Caveat: NgClass and NgStyle
    Every time the view is updated, NgClass and NgStyle directives
    override all other bindings.
    [style]="{ background: 'rebeccapurple' }">
    This element is red.

    View Slide

  14. Caveat: Nullish values
    Style bindings override lower priorities when bound to null.
    [style]="{ background: 'rebeccapurple' }">
    This element is transparent.

    View Slide

  15. Caveat: Nullish values
    Style bindings skip to lower priorities when bound to undefined.
    [style]="{ background: 'rebeccapurple' }">
    This element is purple.

    View Slide

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

    View Slide

  17. Style binding ordering
    Style binding ordering does not affect style evaluation.
    [style.background]="'blue'">
    This element is blue.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. Strict template type checking

    View Slide

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

    View Slide

  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

    View Slide

  24. strictAttributeTypes
    Angular Language Service
    Attribute-style property bindings are type-checked.

    ~~~~~
    export class CounterComponent {
    @Input()
    count = 0;
    }

    View Slide

  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
    ~~~~~
    projects/strict-app/src/app/app.component.ts:7:16
    7 templateUrl: './app.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component AppComponent.

    View Slide

  26. 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;
    }

    View Slide

  27. 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;
    }

    View Slide

  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
    ~~
    projects/shared/src/lib/user-list.component.ts:10:16
    10 templateUrl: './user-list.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component UserListComponent.

    View Slide

  29. 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();
    }
    }

    View Slide

  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
    ~~~~~~
    projects/shared/src/lib/button.component.ts:7:16
    7 templateUrl: './button.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component ButtonComponent.

    View Slide

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

    ~~~~

    View Slide

  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
    ~~~~
    projects/shared/src/lib/message-form.component.ts:7:16
    7 templateUrl: './message-form.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component MessageFormComponent.

    View Slide

  33. 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.

    View Slide

  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
    ~~~~~
    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.

    View Slide

  35. strictInputTypes
    Angular Language Service
    Input property bindings are type-checked.

    ~~~~~~~
    export class MessageFormComponent {
    @Input()
    message = '';
    }

    View Slide

  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
    ~~~~~~~
    projects/strict-app/src/app/app.component.ts:7:16
    7 templateUrl: './app.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component AppComponent.

    View Slide

  37. 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.

    View Slide

  38. 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.

    View Slide

  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
    ~~
    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.

    View Slide

  40. 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' },
    ]);
    }

    View Slide

  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
    ~~~~~
    projects/strict-app/src/app/app.component.ts:9:16
    9 templateUrl: './app.component.html',
    ~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component AppComponent.

    View Slide

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

    View Slide

  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.

    View Slide

  44. 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();
    }
    }

    View Slide

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

    View Slide

  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.

    View Slide

  47. 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;
    }
    }

    View Slide

  48. 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; // 👈
    }
    }

    View Slide

  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.

    View Slide

  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"]
    }

    View Slide

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

    View Slide

  52. Conclusion

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide

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

    View Slide

  60. Get in touch 👋
    Lars Gyrup Brink Nielsen
    🐦 @LayZeeDK

    View Slide

  61. View Slide