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

nigrosimone

Nigro Simone

Posted on March 31, 2022

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

πŸ’– πŸ’ͺ πŸ™… 🚩
nigrosimone
Nigro Simone

Posted on March 31, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related