16

Could someone please explain what's behind the following behavior?

Say we have an Angular 2 component that has a _model object. Then in the template we have this:

<form>
    <input type="text" class="form-control" required [(ngModel)]="_model.firstName" ngControl="test2"  #myInput >
    <br>Class: {{myInput?.className}}
</form>

The _model is available from the beginning being created from scratch in ngOnInit(). The input field is properly populated with the _model.firstName variable and the line:

<br>Class: {{myInput?.className}}

correctly renders the following in the template:

Class: form-control ng-untouched ng-pristine ng-invalid.

So far so good. What confuses me is that the moment I add *ngIf and I change the input field to

<input *ngIf="_model" type="text" class="form-control" required [(ngModel)]="_model.firstName" ngControl="test2"  #myInput >

The double curly braces interpolation stops working because apparently the local myInput variable doesn't get initialized even when nothing else in the code changes, the _model object is still created in onNgInit() and the input field is still working properly. The only thing that the {{myInput?.className}} renders is

Class:

Can someone explain what's going on and/or point me to the correct piece of documentation for this?

EDIT:

Here's a Plunker that shows the issue in question.

Created bug report https://github.com/angular/angular/issues/8087

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
RVP
  • 2,330
  • 4
  • 23
  • 34
  • is your _model a boolean? – Pratik Kelwalkar Apr 15 '16 at 09:01
  • Just tested it in my application, and I can reproduce your issue. Somehow `#myInput` gets `undefined` after you add `*ngIf`. This feels like an angular2 bug, or someone must come up with a good explanation. – Poul Kruijt Apr 15 '16 at 09:10
  • You can however get the element by using `@ViewChild('myInput') myInputRef : ElementRef;`, and `Class: {{myInputRef?.nativeElement?.className}}`, but that does not feel like an appropriate approach – Poul Kruijt Apr 15 '16 at 09:16
  • I'll file a bug with the Angular2 guys - they'll know if that's expected behavior or an issue. Meanwhile I've updated the original post with a plunker. – RVP Apr 15 '16 at 09:29
  • @RVP try long form of `ngIf` directive, surround `input` with `` tag. – tchelidze Apr 15 '16 at 09:53
  • Thanks @tchelidze but that does not seem to work either. – RVP Apr 15 '16 at 09:56
  • [this is interesting](http://plnkr.co/edit/q9iAE1FmjfuLCke93wYu?p=preview), it doesn't work but still interesting – Abdulrahman Alsoghayer Apr 15 '16 at 10:18
  • 1
    @GünterZöchbauer let's say we use `ngFor` directive instead of `ngIf`, naming an element and using that names outside of `ngFor` context will not makes sense (*since there will be potentially more that one element with that name*). same goes here, Consider [following](http://plnkr.co/edit/Rvc61V?p=preview), element's name is available inside structural directive's context, but not outside of it. This makes sense, since Angular can't know whether specific directive will repeat his template or not. – tchelidze Apr 15 '16 at 10:33
  • 2
    @tchelidze Sounds like an explanation – Günter Zöchbauer Apr 15 '16 at 10:37

1 Answers1

36

We can reference a local template variable on the same element, on a sibling element, or on any child elements. -- ref

*ngIf becomes/expands to

<template [ngIf]="_model">
    <input type="text" class="form-control" required [(ngModel)]="_model.firstName"
     ngControl="test1" #myInput>
</template>

So local template variable #myInput can only be referenced inside the template block (i.e., sibling and/or child elements). Hence you would have to put any HTML that wants to reference the local template variable inside the template:

<template [ngIf]="_model">
   <input type="text" class="form-control" required [(ngModel)]="_model.firstName"
    ngControl="test1"  #myInput >
   <br>Class (this works): {{myInput?.className}}
</template>

Plunker


If you need to show something outside the template block related to the input, use @ViewChildren('myInput') list:QueryList<ElementRef> and then subscribe to changes:

ngAfterViewInit() {
   this.list.changes.subscribe( newList =>
      console.log('new list size:', newList.length)
   )
}

See more QueryList methods in the API doc.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492