Как объединить ссылочную переменную шаблона с ngIf?

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

Несмотря на то, что *ngIf соответствует действительности, я получаю Cannot read property 'foo' of undefined. Если я удаляю *ngIf, он работает нормально!

Я попытался использовать оператор Элвиса tRefVar?.foo, который разрешил ошибку, но затем он никогда не обновляется со значением.

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

Что я делаю неправильно?

Ответ 1

Как сказал Тобиас Босх

Переменная, объявленная внутри * 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);
      });
}

enter image description here

здесь нет встроенного представления. Все в одном 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);
      });
}

enter image description here

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

<div *ngIf="true" myHighlight #tRefVar="myHighlight"></div>
Здесь вы должны заметить, что *ngIf является синтаксическим сахаром (ярлык) для определения a 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 доступен Child (div здесь) и сам (ng-template здесь).
Второй <div> не является дочерним элементом <div>, где присутствует ссылочная переменная шаблона.
Подробнее объяснено здесь


Ожидается поведение, так как ссылочная переменная шаблона может ссылаться на элементы Child/Sibling.

Ответ 3

Вы можете использовать, например, 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

Есть, вероятно, больше способов заставить его работать, но тогда вам нужно быть более конкретным, что вы хотите с ним сделать.

Надеюсь, что смогу помочь.


Чтобы ответить на вопрос в вашем комментарии "Не следует ли обновлять tRefVar после этого первого галочка?":

Нет, потому что это undefined. У вас будет тот же результат, если вы объявите объект в своем компоненте (но оставьте его undefined) и добавьте к нему свойство. Оператор elvis там не помогает.

myObj: any;

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

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

Это не сработает, но это будет работать:

myObj: any = {};

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

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

Отредактировал мой ответ еще раз и устранил запутанное объяснение, которое было просто неправильно. спасибо юрзуи, наконец, понял, что вы имели в виду. нужна ночь для сна.

Ответ 4

Как указано в выше ответ by @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

Ответ 5

Если вы используете 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 будет значением по умолчанию.