Home

Published

- 4 min read

Strongly typed ng-template in Angular

Strongly typed ng-template in Angular
Liked 4 times

If you're like me and feel uneasy whenever something is typed as 'any' in TypeScript, this article is for you! I'll show you a neat trick to help both the compiler and your IDE understand the exact type of data being passed to your ng-template.

The problem

When you have a <ng-template> that accepts parameters via context, you usually lose TypeScript's type safety, reverting to the prehistoric age of JavaScript with no type enforcement:

<ng-template #someTemplate let-someVariable="someVariable">
  {{Math.abs(someVariable)}} <!-- compiler and IDE have no idea that the variable is a string -->
</ng-template>

With this approach, you can perform any operation on someVariable, and the compiler won't warn you—even if it results in runtime errors.

The solution

To ensure type safety, we can create a type assertion guard directive:

@Directive({
  selector: 'ng-template[some-template]',
  standalone: true,
})
export class SomeTemplateNgTemplate {
  static ngTemplateContextGuard(
    directive: SomeTemplateNgTemplate,
    context: unknown
  ): context is {someVariable: string} {
    return true;
  }
}

Explanation

  1. Directive setup
    • This directive applies to <ng-template> elements that include the some-template attribute (ng-template[some-template] in the selector).
    • It's marked as standalone, which is the recommended approach in modern Angular.
  2. Type Context Guard
    • The class name is not important and can be anything.
    • The static ngTemplateContextGuard function is where the magic happens.
    • It must accept two parameters:
      • An instance of itself (directive: SomeTemplateNgTemplate).
      • The context (which is typed as unknown which is a more type-safe any).
    • The return type uses a TypeScript type predicate, which tells the compiler: If this function returns true, then the context must match the given type { someVariable: string }.

Since this function always returns true, TypeScript will assume that every template using this directive has the expected type.

Important note: As with all TypeScript type assertions, this is a compile-time safety measure—it does not enforce types at runtime. You can still pass invalid values, but TypeScript will warn you beforehand.

Applying the Directive

Now, update your template to use the directive:

<ng-template some-template #someTemplate let-someVariable="someVariable">
  {{Math.abs(someVariable)}}
</ng-template>

The result

With the some-template directive in place, Angular now correctly infers the type of someVariable. If you try to use Math.abs(someVariable), TypeScript will now show an error:

NG5: Argument of type 'string' is not assignable to parameter of type 'number'.

Conclusion

By leveraging ngTemplateContextGuard, you can enforce strong typing within ng-template contexts, making your Angular code safer and more maintainable. This simple trick helps catch potential errors at compile time rather than at runtime—ensuring better developer experience and fewer unexpected bugs.

© 2024 Dominik Chrástecký. All rights reserved.