Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use of local template variable within a *ngIf #6179

Closed
Mica4DEV opened this issue Dec 30, 2015 · 26 comments
Closed

Use of local template variable within a *ngIf #6179

Mica4DEV opened this issue Dec 30, 2015 · 26 comments

Comments

@Mica4DEV
Copy link

Hi,

With the beta 0, with a template like that:

<input #test *ngIf="isVisible">
<button (click)="focusInput(test)" *ngIf="isVisible">Focus it</button>

Even if boolValue is true (the input will be visible), test will be "undefined" in the click handler.

The only "fix" someone gave me was to make a directive to autofocus.
The problem that everytime I will need to access a field that may be hidden by a * [embedded template], I won't be able to use it, even if in plain javascript I could test if its value was defined or not.

Plunker link:
http://plnkr.co/edit/JmBBHMo1hXLe4jrbpVdv?p=preview

Is this a "bug" or an expected behaviour?
Is there a more efficient way to deal with this?

@bennylut
Copy link

As I see it - the test variable in focusInput(test) refers to the field test of the component class - since such field is not defined you are getting undefined passed to the focusInput method.

One fix that I know about is to use the ViewChild annotation and to actually declare the test field:

@Componenet({
  template: `
    <input #test *ngIf="isVisible">
    <button (click)="focusInput(test)" *ngIf="isVisible">Focus it</button>
  `,
...})
class MyComponent {
  @ViewChild('test') test;
}

Plunker link:
http://plnkr.co/edit/LaRpasKQasO6vf2UpxMK?p=preview

@kasperpeulen
Copy link

As I see it - the test variable in focusInput(test) refers to the field test of the component class - since such field is not defined you are getting undefined passed to the focusInput method.

I think this must be a bug with *ngIf because if you use [hidden] instead, everything works fine:

http://plnkr.co/edit/gVhzUEYaXuttK2tVmi2t?p=preview

@Mica4DEV
Copy link
Author

Mica4DEV commented Jan 2, 2016

@bennylut Then why can we do "#test" if we can't use it in the template?
It's still a bug for me, as the documentation clearly states it:

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

<!-- phone refers to the input element; pass its `value` to an event handler -->
<input #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>

@kasperpeulen yes hidden is working because it's the default css properties (display: none;) for browsers associated with the hidden bindings, it has no impact on creating/removing the node from the DOM.

@RomanHotsiy
Copy link

This still doesn't work in RC.3.
@bennylut workaround works fine for this particular example but it doesn't work if local reference is specified inside ForOf as we have lots of references and ViewChild queries the first.

this works:

<div *ngFor="let thing of things">
  <span (click)="props.toggle()">
    {{thing.name}}
  </span>
  <div>
    <collapse #props>
      something
    </collapse>
  </div>
</div>

and this doesn't (even if thing.extra is true)

<div *ngFor="let thing of things">
   <span (click)="props.toggle()">
    {{thing.name}}
  </span>
  <div *ngIf="thing.extra">
    <collapse #props>
      something
    </collapse>
  </div>
</div>

Check out Plunker

@Splaktar
Copy link
Member

I'm seeing this in rc.4: vaadin/vaadin-grid#411

I really hate to have to use display: none or [hidden], but they do actually "work"...

@dereklin
Copy link

Is it right that it used to work in rc.1 and is no longer working in rc.4?

@tbosch
Copy link
Contributor

tbosch commented Jul 18, 2016

@RomanGotsiy your plunker works as intended:

A variable declared inside of an *ngIf cannot be used outside of the *ngIf (as in your plunker), but the other way around works now (to the time when this issue was filed this was not the case though).

I.e. this should work.

<div *ngFor="let thing of things">  
   <span *ngIf="thing.extra" (click)="props.toggle()">
    {{thing.name}}
  </span>
    <collapse #props>
      something
    </collapse>
</div>

@tbosch
Copy link
Contributor

tbosch commented Jul 18, 2016

@tbosch tbosch closed this as completed Jul 18, 2016
@poke
Copy link

poke commented Jul 18, 2016

@tbosch I’m not sure why you are closing this. This seems to be a desired functionality (the one outlined in the original post on this issue).

@Splaktar
Copy link
Member

I guess that I'll have to open a separate issue for my problem mentioned here: #6179 (comment)

@Splaktar
Copy link
Member

Tried to reproduce my issue in a Plunkr but wasn't able to do so with an Angular 2 component.

It seems to only occur with angular2-polymer or vaadin-grid elements. So I've re-opened my issue here: vaadin/vaadin-grid#411

@tbosch
Copy link
Contributor

tbosch commented Jul 19, 2016

@poke Yes, the example in my comment shows the desired behavior and the plunker shows that this is working now. 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.

@lindsaymarkward
Copy link

Can I confirm that the plan/design is for this to never work?

<div *ngIf="x.y">...</div>
<app-component #x></app-component>

when this does work?

<div [hidden]="x.y">...</div>
<app-component #x></app-component>

(where y is a variable within app-component that x refers to)

It seems sad that we can set the hidden property but not remove the component with *ngIf.

@poke
Copy link

poke commented Aug 3, 2016

@lindsaymarkward No, that example does work (see plunkr).

What will not work is if the component that declares a reference is within an ngIf, e.g.:

<div *ngIf="show">
  <div *ngIf="x.y">...</div>
</div>
<div *ngIf="show">
  <!-- this makes the #x conditional, so is not available to others outside -->
  <app-component #x></app-component>
</div>

@lindsaymarkward
Copy link

lindsaymarkward commented Aug 3, 2016

Thanks, that's what I meant, actually, but I got it wrong when I typed it. My code example was intended to be like yours with the div surrounding the component.
So, what is the pattern for conditionally showing/including a component (or element that surrounds it) based on the data in that component?

@brendanarnold
Copy link

Why is this closed? The problem still exists i.e. in the template ...

<div *ngIf="showMyDiv">
  <h1 #mytitle>My Title</h1>
</div>

and in the component class ....

@ViewChild('mytitle') title;

title is always undefined if showMyDiv starts out false but later set to true

This is a pretty common scenario and means I have to resort to binding to hidden. Am I missing something?

@michaelb-01
Copy link

I also have this issue, is there a solution yet?

@DzmitryShylovich
Copy link
Contributor

use @ViewChildren. it returns QueryList which will notify u when element is present on the page.

@michaelb-01
Copy link

I have this working now. I'm using it on a video element to control the video among other things. I also need to get the width of the video so I tried the following but it returns 241 when the video is actually more like 600px, is it trying to get the width before the element has fully loaded?

ngAfterViewInit() {
this.mediaQuery.changes.subscribe(el => {
this.media = el._results[0].nativeElement;
console.log(this.media.clientWidth);
});
}

@kub1x
Copy link

kub1x commented May 12, 2017

As a solution I found the following. My issue was closest to what what @brendanarnold wrote. I wasn't able to make running the plnkr what @bennylut posted up here.

Say you have following template:

<pre>{{test.value}}</pre>

<button (click)="isVisible = true">Show it</button>
<input #test *ngIf="isVisible">

Then in your component you "grab" the test element like this:

private test: ViewContainerRef;
@ViewChild('test') set someDummySetterName(theElementRef: ViewContainerRef) {
    this.test = theElementRef;
}

The setter is called every time the *ngIf directive changes so the this.test on controller always corresponds to the actual state and, as a controller variable, it is visible everywhere in the template.

Taken from here: http://stackoverflow.com/a/41095677/336753

@riscie
Copy link

riscie commented Oct 31, 2017

This one is old, i know, but one could also use Renderer2 (https://angular.io/api/core/Renderer2) like so anywhere in the code:

(Setting focus for eg.)

TS

 import {Renderer2} from '@angular/core';

 constructor(renderer: Renderer2) {
 }

 getReferenceOfDOMElementAndDoSometing() {
    this.renderer.selectRootElement("#myTextArea").focus();
 }

HTML

<textarea id="myTextArea" *ngIf="someCondition"></textarea>

@moxival
Copy link

moxival commented Feb 26, 2018

Can you guys at least FIX the documentation, because now it is so misleading it makes me cry!

https://angular.io/guide/template-syntax#ref-vars

You can refer to a template reference variable anywhere in the template.

NO you cannot reference a variable anywhere because Angular has contexts within the contexts within......

@gangsthub
Copy link

as:

*ngIf="expression && (expression | transformExpressionFilter) as dynamicallyGeneratedExpression"

https://angular.io/api/common/NgIf#storing-conditional-result-in-a-variable

@toughthinktank
Copy link

toughthinktank commented Aug 23, 2018

Using openModal type of method where modal has *ngIf condition will set the native element to undefined as the native element is only created when the condition mentioned with *ngIf becomes true. But there is a way around this if you absolutely have to use *ngIf by using Javascript's event loop manipulation using setTimeout or Promises or any sort of callbacks. Here's an example

HTML Code

<button (click)="openModal()">Open Modal</button>

<div class="modal fade" #sampleModal *ngIf="showModal">
       <!--Modal View goes here-->
</div>

TS Code

//Inside export component class
showModal:boolean = false;

openModal = function () {
        this.showModal = true;
        var that = this;
        setTimeout(() => {that.showHideModal('sampleModal', 'show')  }, 0)
}

showHideModal(id, showOrHide) {
        $(this[id].nativeElement).modal(showOrHide);
}

Now you won't get the 'nativeElement undefined error' even with *ngIf. Why does this work? It is because of the angular/JS event loop. Basically *ngIf ensures that the modal with the id as 'sampleModal' only comes into the DOM as a native element when the boolean 'showModal' becomes true. Therefore, the command for actually showing the Modal which is the 'showHideModal' function in the given code must be a part of the next loop. While the openModal function is running, the setTimeout will set its calling function at the end of the loop, i.e. after the current function call stack is over. Therefore, we also need to pass in the context of the showHideModal function for it to run in the given scope. So, we use a variable 'that' and set it to the execution context 'this' of the class in which it is defined and pass that.showHideModal in the setTimeout. So, by the time setTimeout executes its callback function, the *ngIf condition has become true and the modal is present in the DOM to be shown or hidden by the 'showHideModal' function.
Hope it helped! :)

@ocvx
Copy link

ocvx commented Nov 27, 2018

@kub1x solution worked for me, but in the case the var is used as an input to another child you should use change detection:

<app-child1 #ch1 *ngIf="loadChild"></app-child1>
<app-child2  [brother]="ch1"> </app-child2>
@ViewChild('ch1') set ch(ch: Child1Component){
  this.ch1 = ch;
  this.cdRef.detectChanges(); //update child2's 'brother' input
}
constructor(private cdRef:ChangeDetectorRef){
  setTimeout(() => this.loadChild = true, 2000);
}

Stackblitz example

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests