如何将模板引用变量与ngIf结合使用?

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

即使*ngIf为真,我仍然会收到Cannot read property 'foo' of undefined的错误。如果我删除*ngIf,它就可以正常工作! 我尝试使用Elvis运算符tRefVar?.foo,它解决了错误,但它永远不会更新值。

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

我做错了什么?

你可以简单地使用 [ngStyle]="{'display': showElement ? 'block' : 'none'}" 或使用任何其他默认的显示选项(flex,inline ...)。 - Andre Elrico
5个回答

67

正如Tobias Bosch所说

* ngIf内声明的变量不能在*ngIf外部使用

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

只有相反的方式(即在*ngIf内部声明一个变量并在*ngIf外部使用它)是不起作用的,并且这是设计上不要这样做。

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

为什么会这样呢?

1)没有*ngIf

让我们来看一下这个模板

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

Angular会为此创建以下viewDefinition

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

在此处输入图像描述

这里没有嵌入式视图。全部都在一个名为View_App_0的视图中。我们可以看到这里的表达式是{{tRefVar?.foo}}

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

它从索引为2的节点获取值。

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

在同一视图中声明的内容

2) 使用*ngIf指令

然后让我们按照以下方式更改模板

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

输出结果如下:

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创建了一个名为View_App_1的嵌入式视图,与View_App_0分开。我们的表达式{{tRefVar?.foo}}已经变成:

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

因为在View_App_0中没有节点引用这个模板变量,所以它只成为了组件属性,并且被转移到嵌入式视图View_App_1中。

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

因此,我们无法在嵌入式视图之外引用已在嵌入式视图中声明的模板变量。

如何解决?

1)使用可见性标志,如[hidden]或CSS类,而不是*ngIf

2)将您的表达式移动到声明tRefVar的嵌入式视图内部。

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

3)使用@ViewChild,因为它将表示组件属性。或使用@ViewChildren


为什么您建议在解决方案#2中使用ng-container而不是ng-templatengIf?如果两者都可以,那么在这种情况下选择它们的考虑因素是什么? - Alexander Abakumov
@AlexanderAbakumov,我展示了ngIf的糖化版本,因为这是每个人都能理解的众所周知的语法。您可以使用<ng-template [ngIf]代替,它甚至更好,因为它不会创建额外的注释节点。 - yurzui
明白了,谢谢你特别提到注释节点!只是想确认一下这两个选项之间的区别,因为我在这方面缺乏经验。 - Alexander Abakumov

18
如果您正在使用Angular 8,您可以通过添加一个视图子引用并将静态值设置为false来解决此问题。
示例模板代码:
<button type="button" (click)="eventsTable.someMethod()">Click Me!</button>
<div *ngIf="someValue" #eventsTable >
    SHIBAMBO!
</div>

组件代码:

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

    constructor() {
        console.log(this.eventsTable)
    }
}
在Angular 9中,false将是默认值。

3

<div *ngIf="true" myHighlight #tRefVar="myHighlight"></div>
在这里您需要注意,*ngIf是一个语法糖(快捷方式),用于定义一个ng-template。因此,实际上它等价于:

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

请注意,#tRefVar 可以被子元素(div)和本身(ng-template)访问。
第二个 <div> 不是存放模板引用变量的 <div> 的兄弟元素。
更多详细说明请参见此处
预期行为是可以通过子元素/兄弟元素引用模板引用变量。

我的引用tRefVar.foo仍然在ng-template的同级中,对吗?还是我漏掉了什么? - adamdport
我认为@lexith是正确的,您可以在模板中的任何位置引用模板引用变量 - adamdport
@adamdport https://github.com/angular/angular/issues/13516#issuecomment-267546981 - yurzui

0
您可以使用例如ng-container并将您的ngIf条件设置在那里,这使得tRevVar可以像这样访问:
<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

可能有更多的方法可以使它工作,但是你必须更具体地说明你想要做什么。

希望我能帮到你。


回答你在评论中的问题“不应该在第一次tick后更新tRefVar吗?”:
不,因为它是未定义的。如果您在组件中声明一个对象(但将其保留为未定义状态)并向其添加属性,则会得到相同的结果。Elvis运算符在这里无法帮助。
myObj: any;

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

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

这个不行,但是这个会起作用:

myObj: any = {};

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

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

再次编辑了我的回答并删除了混淆视听、完全错误的解释。感谢yurzui,终于明白你的意思了。需要睡一晚上才能想清楚。


1
由于您的ngIf需要一点时间来评估,我认为它在ng-template块之外是不可用的。参考链接:https://dev59.com/r1oV5IYBdhLWcg3wIb74 https://github.com/angular/angular/issues/16262 - yurzui
你在 ng-template 中插入了 {{tRefVar?.foo}} - yurzui
由于您的ngIf需要一点时间来评估,因此出现了这种情况。这是案例https://plnkr.co/edit/pkF48jOGNRbX2u9RdL4e?p=preview - yurzui
请看我对另一个答案的评论。确实,*ngIfng-template [ngIf] =""的快捷方式,但这不是他无法像他那样访问该值的原因。 - malifa
我明白你想说什么。那 *ngFor 呢?https://plnkr.co/edit/qrIpgbTGVIAcQQTNv3M7?p=preview 还可以看看这个解释 https://github.com/angular/angular/issues/16262#issuecomment-296835766 - yurzui
显示剩余4条评论

0

如@lexith所述的上面的答案,因为您正在使用*ngIf,相应的模板变量在此时未定义。我们可以通过修改代码来避免这种混淆
<div *ngIf="true" myHighlight #tRefVar="myHighlight"></div> 改为
<div [hidden]=false myHighlight #tRefVar="myHighlight"></div> 这样就可以正常工作了。


最终代码:

@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>
  `,
})

修改后的 Plunker


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接