340

I have the following template :

<div>
  <span>{{aVariable}}</span>
</div>

and would like to end up with :

<div "let a = aVariable">
  <span>{{a}}</span>
</div>

Is there a way to do it ?

Scipion
  • 11,449
  • 19
  • 74
  • 139
  • Im interested to know what the requirement/use case is for wanting to change the name of a binding parameter such as this example? – LDJ Jul 26 '16 at 06:47
  • 50
    It's just to prevent repeating something like tab[element].val by instance. I know I can solve the issue in the component, but I was just looking at how to do in the template (even though I may not end up with that solution). – Scipion Jul 26 '16 at 07:33
  • 3
    @LDJ one sample use case: efficiency. Use the sample of https://stackblitz.com/angular/mndroyqrvbk?file=app%2Ftree-checklist-example.ts {{node.item}} in fact the descendantsPartiallySelected() calls descendantsAllSelected(). It means sometime descendantsAllSelected is called twice. If there's a local variable, this can be avoid. – Steven.Xi Dec 03 '18 at 14:48
  • 5
    `
    {{user1|json}} {{user|json}}
    `
    – dasfdsa Mar 19 '19 at 19:09
  • @dasfdsa I believe `user1 === user`, thus you either do `*ngIf="{name:'john'} as user1` or `*ngIf="{name:'john'};let user` as in [yurzui's answer](https://stackoverflow.com/a/43172992/6225838). – CPHPython Jul 30 '19 at 09:54
  • Refer to this https://stackoverflow.com/questions/55726995/angular-6-alias-on-html/60332430#60332430 – San Jaisy Feb 21 '20 at 04:56
  • For i18n localization template rules look [here on SO](https://stackoverflow.com/a/67592925/6085193) – Transformer May 22 '21 at 17:19
  • A common use case will be to get a value from an observable without having to deal with unsubscribing from the component code: `` – Marques Apr 22 '22 at 20:05

20 Answers20

309

Update

We can just create directive like *ngIf and call it *ngVar

your answer was very handy, but here I've adapted it for typing to work too

ng-var.directive.ts

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

@Directive({
  selector: '[ngVar]',
})
export class NgVar<T = unknown> {
  private _context: NgVarContext<T> = new NgVarContext<T>();
  private _viewRef: EmbeddedViewRef<NgVarContext<T>> | null = null;

  @Input()
  set ngVar(context: T) {
    this._context.$implicit = this._context.ngVar = context;

    if (!this._viewRef) {
      this._viewRef = this._viewContainer.createEmbeddedView(
        this._templateRef,
        this._context
      );
    }
  }

  // https://github.com/angular/angular/blob/main/packages/common/src/directives/ng_if.ts

  /**
   * Assert the correct type of the expression bound to the `ngIf` input within the template.
   *
   * The presence of this static field is a signal to the Ivy template type check compiler that
   * when the `NgIf` structural directive renders its template, the type of the expression bound
   * to `ngIf` should be narrowed in some way. For `NgIf`, the binding expression itself is used to
   * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgIf`.
   */

  static ngTemplateGuard_ngVar: 'binding';

  /**
   * Asserts the correct type of the context for the template that `NgIf` will render.
   *
   * The presence of this method is a signal to the Ivy template type-check compiler that the
   * `NgIf` structural directive renders its template with a specific context type.
   */

  // Passing down variable Type
  static ngTemplateContextGuard<T>(
    dir: NgVar<T>,
    ctx: any
  ): ctx is NgVarContext<T> {
    return true;
  }

  constructor(
    private _viewContainer: ViewContainerRef,
    private _templateRef: TemplateRef<NgVarContext<T>>
  ) {}
}

export class NgVarContext<T = unknown> {
  public $implicit: T = null!;
  public ngVar: T = null!;
}

with this *ngVar directive we can use the following

<div *ngVar="false as variable">
      <span>{{variable | json}}</span>
</div>

or

<div *ngVar="false; let variable">
    <span>{{variable | json}}</span>
</div>

or

<div *ngVar="45 as variable">
    <span>{{variable | json}}</span>
</div>

or

<div *ngVar="{ x: 4 } as variable">
    <span>{{variable | json}}</span>
</div>

Plunker Example Angular4 ngVar

See also

Original answer

Angular v4

  1. div + ngIf + let

    {{variable.a}} {{variable.b}}
  2. div + ngIf + as

view

<div *ngIf="{ a: 1, b: 2, c: 3 + x } as variable">
  <span>{{variable.a}}</span>
  <span>{{variable.b}}</span>
  <span>{{variable.c}}</span>
</div>

component.ts

export class AppComponent {
  x = 5;
}
  1. If you don't want to create wrapper like div you can use ng-container

view

<ng-container *ngIf="{ a: 1, b: 2, c: 3 + x } as variable">
  <span>{{variable.a}}</span>
  <span>{{variable.b}}</span>
  <span>{{variable.c}}</span>
</ng-container>

As @Keith mentioned in comments

this will work in most cases but it is not a general solution since it relies on variable being truthy

See update for another approach.

Antony Gibbs
  • 1,321
  • 14
  • 24
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 16
    this will work in most cases but it is not a general solution since it relies on `variable` being truthy – Keith May 31 '17 at 16:46
  • 7
    @Keith Thanks for pointing this out. You can take a look at my updated answer – yurzui May 31 '17 at 17:02
  • 4
    This should be the new answer as it 1) is more modern than the other solution 2) summarizes the pull requests linked in the current answer and saves a lot of time 3) includes the examples inline rather than by external link. Thanks for showing this. AFAIK any object wrapped in `{}` will evaluate to truthy, so this solution is fairly robust. – kvanbere Aug 08 '17 at 01:23
  • 4
    For example, expandable buttons: `*ngIf="{ expanded: false } as scope"` and then if you're using bootstrap you can just use `[ngClass]="{ 'in': scope.expanded }"` and `(click)="scope.expanded = !scope.expanded"` instead of adding anything to your `js`/`ts` files. – kvanbere Aug 08 '17 at 01:26
  • 1
    related github issue (points out using a simple `*ngIf` instead of custom ngvar stuff) : https://github.com/angular/angular/issues/14985 – phil294 Aug 14 '17 at 14:42
  • 6
    I would not use ngVar because of the Angular's namespace. – 0zkr PM Mar 22 '18 at 01:52
  • 1
    @0zkr It's up to you which name use – yurzui Mar 22 '18 at 05:56
  • 1
    When local var change, all template and child component inside will be re-rendered unexpected. This is reproduce the issue at Stackblitz: https://stackblitz.com/edit/angular-srpqsu – Haidang Nguyen Jun 09 '18 at 17:29
  • 1
    There is a [github issue regarding a ngLet directive](https://github.com/angular/angular/issues/15280). In a comment [IgorMinar states](https://github.com/angular/angular/issues/15280#issuecomment-430479166) that implementations using `createEmbeddedView` will result in performance issues and that `Directives are not powerful enough to implement this feature efficiently`. – frido Mar 26 '19 at 12:42
  • This is a great solution for simple cases, however when a ViewChild is enclosed by it, it will become unavailable. – user776686 Jul 01 '19 at 12:26
  • @user776686 Can you please reproduce it in stackblitz? – yurzui Jul 01 '19 at 12:27
  • @yurzui Should be able to come up with a blitz later, but this is a common case with structural directives in general I think. `` - here `myvc` will be undefined. – user776686 Jul 01 '19 at 12:40
  • @user776686 There is no difference with how you should work with queries inside structural directives. They're dynamic queries. https://stackblitz.com/edit/angular-hzeqsy?file=src/app/app.component.ts – yurzui Jul 01 '19 at 13:06
  • what is the difference between `1) div + ngIf + let` and `2) div + ngIf + as` – xx yy Dec 21 '19 at 22:05
  • I use this answer as a reference for some duplicate questions but I find the presentation of the "Update" part somewhat confusing. It forces us to check the original answer and Keith's comment to understand what it means. I would suggest to move the remark about Keith's comment to the "Original answer" section. – ConnorsFan Mar 27 '20 at 14:58
  • Is there a way to avoid this:`The template context of 'VarDirective' does not define a member called 'ngVar'. If the context type is a base type or 'any', consider refining it to a more specific type.` ? – Leonardo Rick Oct 06 '20 at 15:09
  • 2
    @LeonardoRick You can specify type for context similar to how ngIf directive does it. `interface VarContext { $implicit: any; ngVar: any; }` https://github.com/angular/angular/blob/21dfb33b2774817f0031c27d88109b03a44950ed/packages/common/src/directives/ng_if.ts#L153 – yurzui Oct 06 '20 at 15:26
  • I tried to use `*ngVar="mybool as test"` and everytime I update `mybool`, my screen flashes and scroll a bit. If this is normal to happen I think this solution is not the best – Leonardo Rick Oct 08 '20 at 22:32
  • I guess it's because the div is recreated each time this variable is updated. I there a way to avoid that? If it was a ngFor we could override `trackBy` but with this customized directive I don't know how to do that – Leonardo Rick Oct 08 '20 at 22:40
  • 1
    Now that angular has template type checking, is there a way to have the `variable` / `context` can be generic / typed? – ttugates Oct 16 '20 at 16:18
  • using this package's equal function I tweaked the ngVar directive like this to avoid constant change detection. https://www.npmjs.com/package/@calmdownval/slow-deep-equal if (!equal(this.context.ngVar, context, true)) { this.context.$implicit = this.context.ngVar = context; this.updateView(); } – John Mar 10 '21 at 19:37
  • great directive . how to use multiple *ngVar ? – OMANSAK Sep 25 '21 at 23:28
  • 1
    I've added `VarDirective` to my app module's `declarations` but am still seeing `NG0303: Can't bind to 'ngVar' since it isn't a known property of 'ng-container'.`. Any ideas? – trebor Apr 01 '22 at 23:50
  • I think it is so helpful it should he added to Angular :-) – Talha Ahmed Khan Dec 15 '22 at 03:10
125

You can declare variables in html code by using a template element in Angular 2 or ng-template in Angular 4+.

Templates have a context object whose properties can be assigned to variables using let binding syntax. Note that you must specify an outlet for the template, but it can be a reference to itself.

<ng-template #selfie [ngTemplateOutlet]="selfie"
    let-a="aVariable" [ngTemplateOutletContext]="{ aVariable: 123 }">
  <div>
    <span>{{a}}</span>
  </div>
</ng-template>

<!-- Output
<div>
  <span>123</span>
</div>
-->

You can reduce the amount of code by using the $implicit property of the context object instead of a custom property.

<ng-template #t [ngTemplateOutlet]="t"
    let-a [ngTemplateOutletContext]="{ $implicit: 123 }">
  <div>
    <span>{{a}}</span>
  </div>
</ng-template>

The context object can be a literal object or any other binding expression. Other valid examples:

<!-- Use arbitrary binding expressions -->
<ng-template let-sum [ngTemplateOutletContext]="{ $implicit: 1 + 1 }">

<!-- Use pipes -->
<ng-template let-formatPi [ngTemplateOutletContext]="{ $implicit: 3.141592 | number:'3.1-5' }">

<!-- Use the result of a public method of your component -->
<ng-template let-root [ngTemplateOutletContext]="{ $implicit: sqrt(2116) }">

<!--
    You can create an alias for a public property of your component:
    anotherVariable: number = 123; 
-->
<ng-template let-aliased [ngTemplateOutletContext]="{ $implicit: anotherVariable }">

<!--
    The entire context object can be bound from a public property:
    ctx: { first: number, second: string } = { first: 123, second: "etc" }
-->
<ng-template let-a="first" let-b="second" [ngTemplateOutletContext]="ctx">
Steven Liekens
  • 13,266
  • 8
  • 59
  • 85
  • To get it work I had to change your code from ' – Humppakäräjät Dec 31 '17 at 12:43
  • 2
    Yep, you can only use ` – Steven Liekens Jan 01 '18 at 14:26
  • What is the `t` for? – matttm Jun 27 '19 at 18:32
  • 1
    @matttm `#t` is a template variable that stores the `ng-template`. It is used in `[ngTemplateOutlet]="t"` to make the ng-template reference itself. – Steven Liekens Jun 28 '19 at 08:14
  • 3
    This is bizare, but it works! Angular should make this simpler, with a built-in variable directive. Thanks. – TetraDev Jun 30 '20 at 17:00
  • Seems outdated for ng12 – John White Aug 01 '21 at 14:01
  • @JohnWhite how so? I tried my code example in StackBlitz and it still works. – Steven Liekens Aug 03 '21 at 09:08
  • @StevenLiekens it doesn't find ngTemplateOutletContext. I tried it quickly, might be wrong – John White Aug 03 '21 at 10:36
  • 2
    @JohnWhite is it possible you didn't add ``? That's one reason why you might see errors like `Can't bind to 'ngTemplateOutletContext' since it isn't a known property`. – Steven Liekens Aug 04 '21 at 08:19
  • If the expression I want to set as a variable was a function, e.g. `treeControl.isExpanded(node)`, then using `` actually help improve efficiency, e.g. lesser change detections? – Krishnan Oct 21 '21 at 08:36
  • 1
    @Krishnan This is affected by your change detection strategy in the same way as any other binding expression. So `isExpanded(node)` is evaluated for each run of the change detector. There is no real improvement in that regard, unless you bind the result more than once in your template. Derefencing a variable more than once is more efficient than evaluating a function more than once for obvious reasons. – Steven Liekens Oct 21 '21 at 11:57
103

Ugly, but:

<div *ngFor="let a of [aVariable]">
  <span>{{a}}</span>
</div>

When used with async pipe:

<div *ngFor="let a of [aVariable | async]">
  <span>{{a.prop1}}</span>
  <span>{{a.prop2}}</span>
</div>
kayjtea
  • 2,979
  • 1
  • 20
  • 19
  • 4
    That's the one I came up with instinctively - it works with `*ngFor="let a of [(someStream$ | async).someA]` as well. I guess used with an `` it serves the job quite right! – Angelos Pikoulas Jan 31 '18 at 00:36
  • 6
    In case of `*ngFor`, take in mind that all nested content will be recreated if the variable value changes, until you specify a `trackBy` function which returns the same id for all the values. – Valeriy Katkov Jan 02 '20 at 11:06
  • 1
    Excellent, finally a way of reusing the same `Component` `expression`! If you don't really need the `
    ` and just want to reuse a `Component`'s member/prop, use an `ng-container`, for example – ``
    – Dr1Ku Sep 03 '20 at 07:06
  • my final solution using this aproach was on html: `*ngFor="let var of [myBoolRule]; trackBy:trackFn"`, and on ts: `trackFn() { return true; }`. I guess it doesn't really matter what you return on this function, it just need to be a constant – Leonardo Rick Oct 08 '20 at 22:55
62

update 3

Issue https://github.com/angular/angular/issues/2451 is fixed in Angular 4.0.0

See also

update 2

This isn't supported.

There are template variables but it's not supported to assign arbitrary values. They can only be used to refer to the elements they are applied to, exported names of directives or components and scope variables for structural directives like ngFor,

See also https://github.com/angular/angular/issues/2451

Update 1

@Directive({
  selector: '[var]',
  exportAs: 'var'
})
class VarDirective {
  @Input() var:any;
}

and initialize it like

<div #aVariable="var" var="abc"></div>

or

<div #aVariable="var" [var]="'abc'"></div>

and use the variable like

<div>{{aVariable.var}}</div>

(not tested)

  • #aVariable creates a reference to the VarDirective (exportAs: 'var')
  • var="abc" instantiates the VarDirective and passes the string value "abc" to it's value input.
  • aVariable.var reads the value assigned to the var directives var input.
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Wouldn't it be possible to create a structural directive to do so ? – Scipion Jul 26 '16 at 06:20
  • If you need this repeatedly then a directive might do what you want. A structural directive creates it's own view, that's probably not what you want. – Günter Zöchbauer Jul 26 '16 at 06:22
  • 1
    @GünterZöchbauer, very good stuff. I know that its probably a better practice to have variables calculated/prepared in the `component.ts` file. But its so much easier for me to have them in the view for some cases due to a syncing scheme I am implementing throughout my app. I am taking advantage of the javascript reference rules when different variables point to the same object. – AmmarCSE Nov 17 '16 at 11:12
  • I am getting an error like `There is no directive with "exportAs" set to "var"`. Can any one please tell me what mistake I did? I have used the above directive. – Partha Sarathi Ghosh Feb 03 '17 at 09:57
  • Perhaps you didn't add the directive to `declarations: [...]` of `@NgModule()`. If this is not the problem, please create a new question and provide the code that allows to diagnose the problem. – Günter Zöchbauer Feb 03 '17 at 10:03
  • this answer is hard to read... when it starts to "issue" is nothing clear – serge May 23 '22 at 10:54
14

I would suggest this: https://medium.com/@AustinMatherne/angular-let-directive-a168d4248138

This directive allow you to write something like:

<div *ngLet="'myVal' as myVar">
  <span> {{ myVar }} </span>
</div>
raythurnevoid
  • 2,652
  • 1
  • 25
  • 24
  • 3
    like this but * is reserved for structural directives which this is not, anyway +1 – danday74 Sep 13 '18 at 08:58
  • I like the simplicity of this approach. However, It didn't work for me in Angular 13, but it works if I replace *ngLet by *ngIf, as suggested by other answers – Raphael Pinel May 09 '22 at 05:46
13

In case if you want to get the response of a function and set it into a variable, you can use it like the following in the template, using ng-container to avoid modifying the template.

<ng-container *ngIf="methodName(parameters) as respObject">
  {{respObject.name}}
</ng-container>

And the method in the component can be something like

methodName(parameters: any): any {
  return {name: 'Test name'};
}
Philip John
  • 5,275
  • 10
  • 43
  • 68
  • 7
    I know this is old but if anybody is reading this : don't do it; don't write function calls in template (except for events). The function will be executed at every lifecycle because angular cannot detect the change – Alfred Sep 07 '20 at 08:01
12

Here is a directive I wrote that expands on the use of the exportAs decorator parameter, and allows you to use a dictionary as a local variable.

import { Directive, Input } from "@angular/core";
@Directive({
    selector:"[localVariables]",
    exportAs:"localVariables"
})
export class LocalVariables {
    @Input("localVariables") set localVariables( struct: any ) {
        if ( typeof struct === "object" ) {
            for( var variableName in struct ) {
                this[variableName] = struct[variableName];
            }
        }
    }
    constructor( ) {
    }
}

You can use it as follows in a template:

<div #local="localVariables" [localVariables]="{a: 1, b: 2, c: 3+2}">
   <span>a = {{local.a}}</span>
   <span>b = {{local.b}}</span>
   <span>c = {{local.c}}</span>
</div>

Of course #local can be any valid local variable name.

Aaron
  • 241
  • 3
  • 6
  • Doesn't pass a 'production' build as is (also shows as errors by IDEs). Add `[key: string]: any;` to the `Class` to get around this. – Charly Sep 04 '19 at 21:28
12

A simple solution that worked for my requirement is:

 <ng-container *ngIf="lineItem.productType as variable">
       {{variable}}
 </ng-container>

OR

 <ng-container *ngIf="'ANY VALUE' as variable">
       {{variable}}
  </ng-container>

I am using Angular version: 12. It seems it may work with other version as well.

Abhishek Singh
  • 1,367
  • 1
  • 22
  • 46
11

If you need autocomplete support from within in your templates from the Angular Language Service:

Synchronous:

myVar = { hello: '' };

<ng-container *ngIf="myVar; let var;">
  {{var.hello}}
</ng-container>

Using async pipe:

myVar$ = of({ hello: '' });

<ng-container *ngIf="myVar$ | async; let var;">
  {{var.hello}}
</ng-container>
Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
6

With Angular 12 :

  <div *ngIf="error$ | async as error">
     <span class="text-warn">{{error.message}}</span>
   </div>
De Bonheur
  • 742
  • 10
  • 13
5

I was trying to do something similar and it looks like this has been fixed in newer versions of angular.

    <div *ngIf="things.car; let car">
      Nice {{ car }}!
    </div>
    <!-- Nice Honda! -->
metric152
  • 422
  • 4
  • 16
  • This is nice, but I think it is more of a work around, a neat trick, than a true solution. Because declaring a variable should also be possible, IMO, without a condition. As the side effect is that the element is not rendered without the car in this case. But what if the variable I would like to have declared, and reuse multiple times in the template, is optional? The structural `if` directive suggests 2 'template' paths. But in my case the template is always the same, as there is no condition. – Mike de Klerk Jan 04 '22 at 13:12
4

I liked the approach of creating a directive to do this (good call @yurzui).

I ended up finding a Medium article Angular "let" Directive which explains this problem nicely and proposes a custom let directive which ended up working great for my use case with minimal code changes.

Here's the gist (at the time of posting) with my modifications:

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

interface LetContext <T> {
  appLet: T | null
}

@Directive({
  selector: '[appLet]',
})
export class LetDirective <T> {
  private _context: LetContext <T> = { appLet: null }

  constructor(_viewContainer: ViewContainerRef, _templateRef: TemplateRef <LetContext <T> >) {
    _viewContainer.createEmbeddedView(_templateRef, this._context)
  }

  @Input()
  set appLet(value: T) {
    this._context.appLet = value
  }
}

My main changes were:

  • changing the prefix from 'ng' to 'app' (you should use whatever your app's custom prefix is)
  • changing appLet: T to appLet: T | null

Not sure why the Angular team hasn't just made an official ngLet directive but whatevs.

Original source code credit goes to @AustinMatherne

Keego
  • 3,977
  • 1
  • 16
  • 9
4

For those who decided to use a structural directive as a replacement of *ngIf, keep in mind that the directive context isn't type checked by default. To create a type safe directive ngTemplateContextGuard property should be added, see Typing the directive's context. For example:

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

@Directive({
    // don't use 'ng' prefix since it's reserved for Angular
    selector: '[appVar]',
})
export class VarDirective<T = unknown> {
    // https://angular.io/guide/structural-directives#typing-the-directives-context
    static ngTemplateContextGuard<T>(dir: VarDirective<T>, ctx: any): ctx is Context<T> {
        return true;
    }

    private context?: Context<T>;

    constructor(
        private vcRef: ViewContainerRef,
        private templateRef: TemplateRef<Context<T>>
    ) {}

    @Input()
    set appVar(value: T) {
        if (this.context) {
            this.context.appVar = value;
        } else {
            this.context = { appVar: value };
            this.vcRef.createEmbeddedView(this.templateRef, this.context);
        }
    }
}

interface Context<T> {
    appVar: T;
}

The directive can be used just like *ngIf, except that it can store false values:

<ng-container *appVar="false as value">{{value}}</ng-container>

<!-- error: User doesn't have `nam` property-->
<ng-container *appVar="user as user">{{user.nam}}</ng-container>

<ng-container *appVar="user$ | async as user">{{user.name}}</ng-container>

The only drawback compared to *ngIf is that Angular Language Service cannot figure out the variable type so there is no code completion in templates. I hope it will be fixed soon.

Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123
2

I am using angular 6x and I've ended up by using below snippet. I've a scenerio where I've to find user from a task object. it contains array of users but I've to pick assigned user.

<ng-container *ngTemplateOutlet="memberTemplate; context:{o: getAssignee(task) }">
</ng-container>
<ng-template #memberTemplate let-user="o">
  <ng-container *ngIf="user">
    <div class="d-flex flex-row-reverse">
      <span class="image-block">
        <ngx-avatar placement="left" ngbTooltip="{{user.firstName}} {{user.lastName}}" class="task-assigned" value="28%" [src]="user.googleId" size="32"></ngx-avatar>
      </span>
    </div>
  </ng-container>
</ng-template>
Paul Rooney
  • 20,879
  • 9
  • 40
  • 61
The Mechanic
  • 2,301
  • 1
  • 26
  • 37
1

Short answer which help to someone

  • Template Reference variable often reference to DOM element within a template.
  • Also reference to angular or web component and directive.
  • That means you can easily access the varible anywhere in a template

enter image description here

enter image description here

  • Declare reference variable using hash symbol(#)
  • Can able to pass a variable as a parameter on an event

enter image description here

  show(lastName: HTMLInputElement){
    this.fullName = this.nameInputRef.nativeElement.value + ' ' + lastName.value;
    this.ctx.fullName = this.fullName;
  }

*However, you can use ViewChild decorator to reference it inside your component.

import {ViewChild, ElementRef} from '@angular/core';

Reference firstNameInput variable inside Component

@ViewChild('firstNameInput') nameInputRef: ElementRef;

After that, you can use this.nameInputRef anywhere inside your Component.

Working with ng-template

In the case of ng-template, it is a little bit different because each template has its own set of input variables.

enter image description here

https://stackblitz.com/edit/angular-2-template-reference-variable

Manoj
  • 2,000
  • 2
  • 16
  • 21
1

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

Structural directive for sharing data as local variable into html component template.

Source code:

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

interface NgLetContext<T> {
    ngLet: T;
    $implicit: T;
}

@Directive({
    // tslint:disable-next-line: directive-selector
    selector: '[ngLet]'
})
export class NgLetDirective<T> {

    private context: NgLetContext<T | null> = { ngLet: null, $implicit: null };
    private hasView: boolean = false;

    // eslint-disable-next-line no-unused-vars
    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;
    }
}

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));
}
Simone Nigro
  • 4,717
  • 2
  • 37
  • 72
1

Try like this

<ng-container
     [ngTemplateOutlet]="foo"
     [ngTemplateOutletContext]="{ test: 'Test' }"
></ng-container>

<ng-template #foo let-test="test">
    <div>{{ test }}</div>
</ng-template>
0

original answer by @yurzui won't work startring from Angular 9 due to - strange problem migrating angular 8 app to 9. However, you can still benefit from ngVar directive by having it and using it like

<ng-template [ngVar]="variable">
your code
</ng-template>

although it could result in IDE warning: "variable is not defined"

0

The Answer from yurzui was great already. Here is just a small addition.

To make the directive *ngVar typesafe, you can add the static method ngTemplateContextGuard to the directive:

@Directive({
    // eslint-disable-next-line @angular-eslint/directive-selector
    selector: '[ngVar]'
})
export class VarDirective<Type> {
    @Input()
    set ngVar(context: Type) {
        console.log(context)
        this.context.$implicit = this.context.ngVar = context

        if (!this.hasView) {
            this.vcRef.createEmbeddedView(this.templateRef, this.context)
            this.hasView = true
        }
    }

    private context: {
        $implicit: Type
        ngVar: Type
    } = {
        $implicit: null,
        ngVar: null
    }

    private hasView: boolean = false

    constructor(private templateRef: TemplateRef<NgVarContext<Type>>, private vcRef: ViewContainerRef) {}

    // this magic is how we tell Angular the context type for this directive, which then propagates down to the type of the template
    static ngTemplateContextGuard<Type>(dir: VarDirective<Type>, ctx: NgVarContext<Type>): ctx is NgVarContext<Type> {
        return true
    }
}

interface NgVarContext<T> {
    $implicit: T
    ngVar: T
}

Now IDEs like VisualStudioCode can help you with autocompletion, because they know the type of the variable.

-1

It is much simpler, no need for anything additional. In my example I declare variable "open" and then use it.

   <mat-accordion class="accord-align" #open>
      <mat-expansion-panel hideToggle="true" (opened)="open.value=true" (closed)="open.value=false">
        <mat-expansion-panel-header>
          <span class="accord-title">Review Policy Summary</span>
          <span class="spacer"></span>
          <a *ngIf="!open.value" class="f-accent">SHOW</a>
          <a *ngIf="open.value" class="f-accent">HIDE</a>
        </mat-expansion-panel-header>
        <mat-divider></mat-divider>
        <!-- Quote Details Component -->
        <quote-details [quote]="quote"></quote-details>
      </mat-expansion-panel>
    </mat-accordion>
Jack Rus
  • 145
  • 2
  • 10