DEV Community

Nigro Simone
Nigro Simone

Posted on

Angular structural directive for sharing data as local variable into html component template

Sometime there is a need to share data into component template as local variable.

In this example timer$ is subscribed two time:

import { Component } from '@angular/core'; import { defer, Observable, timer } from 'rxjs'; @Component({ selector: 'app-root', template: ` <div> 1: {{ timer$ | async }} <!-- first subscription --> </div> <div> 2: {{ timer$ | async}} <!-- second subscription --> </div> `, }) export class AppComponent { timer$: Observable<number> = defer(() => timer(3000, 1000)); } 
Enter fullscreen mode Exit fullscreen mode

The idea is create a structural directive ngLet for sharing data as local variable into html component template, eg.:

import { Component } from '@angular/core'; import { defer, Observable, timer } from 'rxjs'; @Component({ selector: 'app-root', template: ` <ng-container *ngLet="timer$ | async as time"> <!-- single subscription --> <div> 1: {{ time }} </div> <div> 2: {{ time }} </div> </ng-container> `, }) export class AppComponent { timer$: Observable<number> = defer(() => timer(3000, 1000)); } 
Enter fullscreen mode Exit fullscreen mode

This structural directive create local context of variable that can be used into html template:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; interface NgLetContext<T> { ngLet: T; $implicit: T; } @Directive({ selector: '[ngLet]' }) export class NgLetDirective<T> { private context: NgLetContext<T | null> = { ngLet: null, $implicit: null }; private hasView: boolean = false; constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<NgLetContext<T>>) { } @Input() set ngLet(value: T) { this.context.$implicit = this.context.ngLet = value; if (!this.hasView) { this.viewContainer.createEmbeddedView(this.templateRef, this.context); this.hasView = true; } } /** @internal */ public static ngLetUseIfTypeGuard: void; /** * Assert the correct type of the expression bound to the `NgLet` input within the template. * * The presence of this static field is a signal to the Ivy template type check compiler that * when the `NgLet` structural directive renders its template, the type of the expression bound * to `NgLet` should be narrowed in some way. For `NgLet`, the binding expression itself is used to * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgLet`. */ static ngTemplateGuard_ngLet: 'binding'; /** * Asserts the correct type of the context for the template that `NgLet` will render. * * The presence of this method is a signal to the Ivy template type-check compiler that the * `NgLet` structural directive renders its template with a specific context type. */ static ngTemplateContextGuard<T>(dir: NgLetDirective<T>, ctx: any): ctx is NgLetContext<Exclude<T, false | 0 | '' | null | undefined>> { return true; } } 
Enter fullscreen mode Exit fullscreen mode

With ngLet you can create into html template every variable you want:

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <ng-container *ngLet="(num1 + num2) as total"> <!-- single computation --> <div> 1: {{ total }} <!-- 3 --> </div> <div> 2: {{ total }} <!-- 3 --> </div> </ng-container> `, }) export class AppComponent { num1: number = 1; num2: number = 2; } 
Enter fullscreen mode Exit fullscreen mode

This trick is also published as npm library:
https://www.npmjs.com/package/ng-let

Online demo:
https://stackblitz.com/edit/demo-ng-let

Source code:
https://github.com/nigrosimone/ng-let

Top comments (0)