this post was submitted on 28 Jan 2025
5 points (100.0% liked)

Programming

21 readers
1 users here now

My various programming endeavours, mainly in PHP (Symfony), Typescript (Angular), Go and C#. With a sprinkle of Java and C++ here and there.

founded 3 weeks ago
MODERATORS
 

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.

no comments (yet)
sorted by: hot top controversial new old
there doesn't seem to be anything here