Guy E
Guy E

Reputation: 1927

How to use let in an HTML template?

I'm trying to do something which should be simple: I would like to execute a function in an *ngFor. The function returns an object. I would like to set the object in a kind of "let" statement, so I can use its property in the HTML:

<div *ngFor="let productGroup of getproductGroupBySomeVariable(variable)">
            <!-- This is where I need to set a variable with the output of 
             the getProductBySomeProperty function-->
      <div *ngLet="'{{getProductBySomeProperty(productGroup.someproperty)}}' 
              as  myVar" class="ui-g">
              <!-- Here I want to use and display the properties of the 
                object created by the function above-->
            <span>{{myVar.property1}} </span>
            <span>{{myVar.property2}} </span> etc....
      </div>
</div>

Upvotes: 5

Views: 10059

Answers (3)

Simone Nigro
Simone Nigro

Reputation: 4897

I'm the author of https://www.npmjs.com/package/ng-let

source code of the directive: https://github.com/nigrosimone/ng-let/blob/main/projects/ng-let/src/lib/ng-let.directive.ts

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<T> {
        return true;
    }
}

Usage:

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

Upvotes: 3

Ivan Sanz Carasa
Ivan Sanz Carasa

Reputation: 1387

Just wanted to mention an alternative solution that works really well:

  • single subscription
  • works with falsy values
  • is strongly typed
<ng-container *ngIf="{ user: user | async } as context"> <!-- always true -->
    {{ context.user }}
</ng-container>

Upvotes: 7

NiZa
NiZa

Reputation: 3926

You can create a directive for this, and use it this way:

Directive

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

interface ILetContext<T> {
    ngLet: T;
}

@Directive({
    selector: '[ngLet]'
})
export class LetDirective<T> {
    private context: ILetContext<T> = { ngLet: undefined };

    constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef<ILetContext<T>>) {
        viewContainer.createEmbeddedView(templateRef, this.context);
    }

    @Input()
    private set ngLet(value: T) {
        this.context.ngLet = value;
    }
}

Html

<ng-container *ngLet="getproductGroupBySomeVariable(variable) as productGroup;"></ng-container>

Upvotes: 7

Related Questions