49
<div *ngIf="true" myHighlight #tRefVar="myHighlight"></div>
<div>tRefVar is {{tRefVar.foo}}</div>

Even though the *ngIf is true, I get a Cannot read property 'foo' of undefined. If I remove the *ngIf, it works fine!

I tried using the Elvis operator tRefVar?.foo, which resolved the error, but then it never updates with the value.

https://plnkr.co/edit/5rsXygxK1sBbbkYdobjn?p=preview

What am I doing wrong?

adamdport
  • 11,687
  • 14
  • 69
  • 91
  • You can simply use `[ngStyle]="{'display': showElement ? 'block' : 'none'}"` or use any other default display option (flex, inline ...) – Andre Elrico Aug 22 '19 at 06:55

5 Answers5

65

As Tobias Bosch said

A variable declared inside of an *ngIf cannot be used outside of the *ngIf

https://github.com/angular/angular/issues/6179#issuecomment-233374700

Only the opposite way (i.e. declare a variable inside of *ngIf and use it outside of *ngIf) is not working, and won't work by design.

https://github.com/angular/angular/issues/6179#issuecomment-233579605

Why is it so?

1) Without *ngIf

Let's see at this template

<h2 myHighlight #tRefVar="myHighlight">tRefVar is {{tRefVar.foo}}</h2>
<div>tRefVar is {{tRefVar?.foo}}</div>

angular will create the following viewDefinition for that:

function View_App_0(_l) {
  return jit_viewDef1(0,[(_l()(),jit_textDef2(null,['\n      '])),(_l()(),jit_elementDef3(0,
      null,null,2,'h2',[['myHighlight','']],null,null,null,null,null)),jit_directiveDef4(16384,
      [['tRefVar',4]],0,jit_HighlightDirective5,[jit_ElementRef6],null,null),(_l()(),
      jit_textDef2(null,['tRefVar is ',''])),(_l()(),jit_textDef2(null,['\n      '])),
      (_l()(),jit_elementDef3(0,null,null,1,'div',[],null,null,null,null,null)),(_l()(),
          jit_textDef2(null,['tRefVar is ',''])),(_l()(),jit_textDef2(null,['\n  ']))],
      null,function(_ck,_v) {
        var currVal_0 = jit_nodeValue7(_v,2).foo;
        _ck(_v,3,0,currVal_0);
        var currVal_1 = ((jit_nodeValue7(_v,2) == null)? null: jit_nodeValue7(_v,2).foo);
        _ck(_v,6,0,currVal_1);
      });
}

enter image description here

there is no embedded view here. All in one View_App_0. And we can see here our expression {{tRefVar?.foo}}

var currVal_1 = ((jit_nodeValue7(_v,2) == null)? null: jit_nodeValue7(_v,2).foo);

it takes value from node with index 2

jit_directiveDef4(16384,
  [['tRefVar',4]],0,jit_HighlightDirective5,[jit_ElementRef6],null,null),(_l()(),
  jit_textDef2(null,['tRefVar is ','']))

that declared in the same view

2) With *ngIf

Then let's change template as follows

<h2 *ngIf="true" myHighlight #tRefVar="myHighlight">tRefVar is {{tRefVar.foo}}</h2>
<div>tRefVar is {{tRefVar?.foo}}</div>

The output will be the following

function View_App_1(_l) {
  return jit_viewDef1(0,[(_l()(),jit_elementDef2(0,null,null,2,'h2',[['myHighlight',
      '']],null,null,null,null,null)),jit_directiveDef3(16384,[['tRefVar',4]],0,jit_HighlightDirective4,
      [jit_ElementRef5],null,null),(_l()(),jit_textDef6(null,['tRefVar is ','']))],
      null,function(_ck,_v) {
        var currVal_0 = jit_nodeValue7(_v,1).foo;
        _ck(_v,2,0,currVal_0);
      });
}
function View_App_0(_l) {
  return jit_viewDef1(0,[(_l()(),jit_textDef6(null,['\n'])),(_l()(),jit_anchorDef8(16777216,
      null,null,1,null,View_App_1)),jit_directiveDef3(16384,null,0,jit_NgIf9,[jit_ViewContainerRef10,
      jit_TemplateRef11],{ngIf:[0,'ngIf']},null),(_l()(),jit_textDef6(null,['\n'])),
      (_l()(),jit_elementDef2(0,null,null,1,'div',[],null,null,null,null,null)),(_l()(),
          jit_textDef6(null,['tRefVar is ',''])),(_l()(),jit_textDef6(null,['\n  ']))],
      function(_ck,_v) {
        var currVal_0 = true;
        _ck(_v,2,0,currVal_0);
      },function(_ck,_v) {
        var _co = _v.component;
        var currVal_1 = ((_co.tRefVar == null)? null: _co.tRefVar.foo);
        _ck(_v,5,0,currVal_1);
      });
}

enter image description here

Angular created embedded view View_App_1 apart to View_App_0 . And our expression {{tRefVar?.foo}} has turned into

var currVal_1 = ((_co.tRefVar == null)? null: _co.tRefVar.foo);

it just becames component property because there is no node that will reference to this template variable in View_App_0. It's gone to embedded view View_App_1

var currVal_0 = jit_nodeValue7(_v,1).foo;

So we cannot refer to template variable that has been declared in embedded view outside of embedded view.

How to solve it?

1) Use visibility flag like [hidden] or css class instead of *ngIf

2) Move your expression inside embedded view where tRefVar is declared

<ng-container *ngIf="true">
  <h2 myHighlight #tRefVar="myHighlight">tRefVar is {{tRefVar.foo}}</h2>
  <div>tRefVar is {{tRefVar?.foo}}</div>
</ng-container>

3) Use @ViewChild because it will represent component property. Or use @ViewChildren

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Why do you suggest using `ng-container` instead of `ng-template` with `ngIf` in the solution #2? If both are possible, what are consideration to choose between them in this case? – Alexander Abakumov Oct 24 '18 at 14:50
  • @AlexanderAbakumov I showed sugared version of ngIf there because it was easy for everyone to undestrand this well know syntax. You can use ` – yurzui Oct 24 '18 at 18:18
  • Got it, thank you, especially for mentioning about the comment node! Just checking what are differences between the two options due to lack of my experience here. – Alexander Abakumov Oct 24 '18 at 19:53
18

If you are using Angular 8 you can solve this issue by adding a view child reference and setting the static value to false.

Example template code:

<button type="button" (click)="eventsTable.someMethod()">Click Me!</button>
<div *ngIf="someValue" #eventsTable >
    SHIBAMBO!
</div>

Component Code:

export class EventsComponent {
    @ViewChild('eventsTable', {static: false}) eventsTable: Table;

    constructor() {
        console.log(this.eventsTable)
    }
}

In Angular 9, false will be the default value.

Tom Benyon
  • 971
  • 10
  • 15
3

<div *ngIf="true" myHighlight #tRefVar="myHighlight"></div>
Here you should note that *ngIf is a syntactic sugar(shortcut) to define a ng-template, So that actually evaluates to

<ng-template [ngIf]="true">
  <h2 myHighlight #tRefVar="myHighlight">Hello {{name}}, tRefVar is {{tRefVar.foo}}</h2>
</ng-template>
<div>tRefVar is {{tRefVar?.foo}}</div>

Note that #tRefVar is accessible by Child(div here) and itself(ng-template here).
The second <div> is not a sibling to the <div> where Template reference variable is present.
More explained here


The behavior is expected as the Template reference variable can be referenced by Child/Sibling elements.

yurzui
  • 205,937
  • 32
  • 433
  • 399
Manubhargav
  • 546
  • 1
  • 10
  • 21
  • My reference to `tRefVar.foo` *is* still in a sibling of the `ng-template` though right? Or am I missing something? – adamdport Jun 28 '17 at 20:19
  • I think @lexith is right, [You can refer to a template reference variable anywhere in the template](https://angular.io/guide/template-syntax#ref-vars) – adamdport Jun 28 '17 at 20:40
  • @adamdport https://github.com/angular/angular/issues/13516#issuecomment-267546981 – yurzui Jun 28 '17 at 20:45
0

You could use for example ng-container and set your ngIf conditional there, which makes tRevVar accessible like this:

<ng-container *ngIf="true">
     <h2 myHighlight #tRefVar="myHighlight">Hello {{name}}, tRefVar is {{tRefVar.foo}}</h2>
     <div>tRefVar is {{tRefVar?.foo}}</div>
</ng-container>

Plunkr: https://plnkr.co/edit/cqrsDVGwa90o1FGWgE22?p=preview

There are probably more ways to make it work, but you have to be more specific then, what you want to do with it.

Hope i could help.


To answer the question in your comment "Shouldn't it update tRefVar after that first tick though?":

No, because it's undefined. You'll have the same outcome if you declare an object in your component (but leave it undefined) and add a property to it. The elvis operator doesn't help there.

myObj: any;

ngOnInit() {
    myObj.text = 'my Text';
}

<div>{{ myObj?.text }}</div>

This won't work, but this would be working:

myObj: any = {};

ngOnInit() {
    myObj.text = 'my Text';
}

<div>{{ myObj?.text }}</div>

Edited my answer again and removed the confusing explanation which was plain wrong. thanks yurzui, finally got what you meant. needed a night of sleep for it.

malifa
  • 8,025
  • 2
  • 42
  • 57
  • 1
    `that because of your ngIf which takes a tick of time to evaluate` I think it is just unavailable outside `ng-template` block https://stackoverflow.com/questions/36642487/angular2-ngif-and-local-template-variables https://github.com/angular/angular/issues/16262 – yurzui Jun 28 '17 at 19:57
  • But i didn't change it. So no, that's not the case (at least not anymore it seems). Added a plunkr. – malifa Jun 28 '17 at 20:03
  • You inserted `{{tRefVar?.foo}}` inside `ng-template` – yurzui Jun 28 '17 at 20:05
  • @lexith Shouldn't it update `tRefVar` after that first tick though? – adamdport Jun 28 '17 at 20:06
  • `that because of your ngIf which takes a tick of time to evaluate` Here is this case https://plnkr.co/edit/pkF48jOGNRbX2u9RdL4e?p=preview – yurzui Jun 28 '17 at 20:06
  • See my comment to the other answer. It's true that `*ngIf` is a shortcut for `ng-template [ngIf]=""` but this isn't the reason the value can't be accessed like he did. – malifa Jun 28 '17 at 20:33
  • You forgot to refer to me in comments :) – yurzui Jun 28 '17 at 20:35
  • @yurzui keep forgetting that, sorry ;) – malifa Jun 28 '17 at 20:36
  • I got what you want to say. What about *ngFor? https://plnkr.co/edit/qrIpgbTGVIAcQQTNv3M7?p=preview See also this explanation https://github.com/angular/angular/issues/16262#issuecomment-296835766 – yurzui Jun 28 '17 at 20:39
0

As given in the above answer by @lexith, Because you're using *ngIf the corresponding template variable is not defined at the time. We can avoid this confusion by modifying the code
<div *ngIf="true" myHighlight #tRefVar="myHighlight"></div> with
<div [hidden]=false myHighlight #tRefVar="myHighlight"></div> and that will work.


Final code:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2 [hidden]=false myHighlight #tRefVar="myHighlight">Hello {{name}}, tRefVar is {{tRefVar.foo}}</h2>
      <div>tRefVar is {{tRefVar?.foo}}</div>
    </div>
  `,
})

Modified Plunker

Manubhargav
  • 546
  • 1
  • 10
  • 21