tRefVar is {{tRefVar.foo}}
Even though the
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
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);
});
}
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);
});
}
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
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.
<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.
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
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.