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));
}
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));
}
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;
}
}
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;
}
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)