93

I'm trying to access a native element in order to focus on it when another element is clicked (much like the html attribute "for" - for cannot be used on elements of this type.

However I get the error:

TypeError: Cannot read property 'nativeElement' of undefined

I try to console.log the nativeElement in ngAfterViewInit() so that it is loaded but it still throws the error.

I also access nativeElement in the click event handler, so that I can focus the element when another element is clicked - is this possibly what is mucking it up, because it compiles before the view has loaded?.

eg:

ngAfterViewInit() {
    console.log(this.keywordsInput.nativeElement); // throws an error
}

focusKeywordsInput(){
    this.keywordsInput.nativeElement.focus();
}

full Code:

relevant part of the html template being used:

<div id="keywords-button" class="form-group" (click)="focusKeywordsInput()">
    <input formControlName="keywords" id="keywords-input" placeholder="KEYWORDS (optional)"/>
    <div class="form-control-icon" id="keywords-icon"></div>
</div>

component.ts:

import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import {  REACTIVE_FORM_DIRECTIVES, 
          FormGroup, 
          FormBuilder, 
          Validators,
          ControlValueAccessor
        } from '@angular/forms';
import { NumberPickerComponent } from './number-picker.component';
import { DistanceUnitsComponent } from './distance-units.component';
import { MapDemoComponent } from '../shared/map-demo.component';
import { AreaComponent } from './area-picker.component';
import { GoComponent } from './go.component';
import { HighlightDirective } from '../highlight.directive';

@Component({
   selector: 'find-form',
   templateUrl: 'app/find-page/find-form.component.html',
   styleUrls: ['app/find-page/find-form.component.css'],
   directives: [REACTIVE_FORM_DIRECTIVES, 
                NumberPickerComponent, 
                DistanceUnitsComponent, 
                MapDemoComponent, 
                AreaComponent, 
                GoComponent]
})
export class FindFormComponent implements OnInit, AfterViewInit {
   findForm: FormGroup;
   submitted: boolean; // keep track on whether form is submitted
   events: any[] = []; // use later to display form changes
   @ViewChild('keywords-input') keywordsInput;
//comment
   constructor(private formBuilder: FormBuilder, el: ElementRef) {}

   ngOnInit() {
      this.findForm = this.formBuilder.group({
         firstname: ['', [ Validators.required, Validators.minLength(5) ] ],
         lastname: ['', Validators.required],
         keywords: [],
         area: ['', Validators.required],
         address: this.formBuilder.group({
            street: [],
            zip: [],
            city: []
         })
      });

      this.findForm.valueChanges.subscribe(data => console.log('form changes', data));
   }

     ngAfterViewInit() {
    console.log(this.keywordsInput.nativeElement); // throws an error
  }

   focusKeywordsInput(){
      this.keywordsInput.nativeElement.focus();
   }

   save(isValid: boolean) {
      this.submitted = true;
      // check if model is valid
      // if valid, call API to save customer
      console.log(isValid);
   }
}

full html template (probably irrelevant):

<form class="text-uppercase" [formGroup]="findForm" (ngSubmit)="save(findForm.value, findForm.valid)">
    <div class="row is-heading">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group">
            <h2 class="search-filter-heading heading m-x-auto">find vegan</h2>
        </div>
    </div>
    <div class="row has-error-text">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group btn-group" style="height:64px;">
            <div style="position: relative; display: inline-block; width: 100%;">
                <multiselect #multiselect></multiselect>
            </div>
        </div>
    </div>
    <div class="row error-text"  [style.display]="multiselect.selectedCategories.length < 1 && submitted ? 'block' : 'none'">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 form-group input-group btn-group">
            <small>Please select at least 1 category.</small>
        </div>
    </div>
    <div class="row is-heading">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group">
            <h2 class="search-filter-heading heading m-x-auto">within</h2>
        </div>
    </div>
    <div class="row">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group btn-group" style="height:64px;">
            <div style="position: relative; display: inline-block;">
                <number-picker #numberPicker></number-picker>
            </div>
            <distance-units></distance-units>
        </div>
    </div>
    <div class="row is-heading">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group">
            <h2 class="search-filter-heading heading m-x-auto">of</h2>
        </div>
    </div>
    <div class="row has-error-text">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group btn-group" style="height:64px;">
            <div style="position: relative; display: inline-block; width: 100%;">
                <my-area></my-area>
            </div>
        </div>
    </div>
    <div class="row error-text"  [style.display]="multiselect.selectedCategories.length < 1 && submitted ? 'block' : 'none'">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 form-group input-group btn-group">
            <small [hidden]="findForm.controls.firstname.valid || (findForm.controls.firstname.pristine && !submitted)">Please enter an area.</small>
        </div>
    </div>
    <div class="row is-heading">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group">
            <h2 class="search-filter-heading heading m-x-auto">keywords</h2>
        </div>
    </div>
    <div class="row form-group">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group btn-group" style="height:64px;">
            <div style="position: relative; display: inline-block; width: 100%;">
                <div id="keywords-button" class="form-group" (click)="focusKeywordsInput()">
                    <input formControlName="keywords" id="keywords-input" placeholder="KEYWORDS (optional)"/>
                    <div class="form-control-icon" id="keywords-icon"></div>
                </div>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-4 offset-lg-4 input-group btn-group" style="height:64px;">
            <div style="position: relative; display: inline-block; width: 100%;">
                <go></go>
            </div>
        </div>
    </div>
</form>
johnny 5
  • 19,893
  • 50
  • 121
  • 195
BeniaminoBaggins
  • 11,202
  • 41
  • 152
  • 287

11 Answers11

100

@ViewChild('keywords-input') keywordsInput; doesn't match id="keywords-input"

id="keywords-input"

should be instead a template variable:

#keywordsInput

Note that camel case should be used, since - is not allowed in template reference names.

@ViewChild() supports names of template variables as string:

@ViewChild('keywordsInput') keywordsInput;

or component or directive types:

@ViewChild(MyKeywordsInputComponent) keywordsInput;

See also https://stackoverflow.com/a/35209681/217408

Hint:
keywordsInput is not set before ngAfterViewInit() is called

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
95

You'll also get this error if your target element is inside a hidden element. If this is your HTML:

<div *ngIf="false">
    <span #sp>Hello World</span>
</div>

Your @ViewChild('sp') sp will be undefined.

Solution

In such a case, then don't use *ngIf.

Instead use a class to show/hide your element being hidden.

<div [class.show]="shouldShow">...</div>
Community
  • 1
  • 1
user2023861
  • 8,030
  • 9
  • 57
  • 86
  • 1
    How to access field if its hidden via ng-If ? As viewChild is showing undefined even when ngIf is true. – Anil Uttani Oct 17 '18 at 09:29
  • 1
    @AnilUttani I don't remember what I did with this example, but can you use `ngShow` instead? – user2023861 Oct 22 '18 at 22:11
  • 1
    @user2023861 You can use `[class.show]="showSomething"` or an inverse logic `[class.hide]="!showSomething"`. – Fabio Milheiro Nov 08 '18 at 14:39
  • I ran into this when my input element was in a `Material Tab` component, in the tab that was not visible by default (when the parent component loads). Solved it by moving code from `ngAfterView` to the event handler that runs when the relevant tab becomes active. – ebhh2001 Apr 11 '19 at 12:10
  • Thanks so much, I'm new to Angular this *ngIf explain just saved me – Seesi Jul 21 '20 at 09:13
  • Im still getting 'Cannot read property 'focus' of undefined' allthough i call it inside ngAfterView and within a timeout. Also tried [class.show], [hidden], etc. but not win. The entire component is routet, can that also affect the result since not every routet component is opened at the start? – MojioMS Dec 14 '20 at 09:06
  • Yes to my previous question in comments: Router-outlet hostet components also effect the availability of viewchilds such as ngIf does. A solution can be found [here](https://stackoverflow.com/questions/39255311/can-you-use-viewchild-or-similar-with-a-router-outlet-how-if-so) – MojioMS Dec 14 '20 at 09:15
  • I had the same issue, the element was undefined even after calling from ngAfterViewInit, so I have to drop the *ngIf use and look for a different approach to access the element at runtime. I was getting this issue of undefined for – Rajkumar M Dec 15 '20 at 06:53
  • Alternative can use `[hidden]="!showSomething"` – Snowbases Jun 08 '21 at 20:45
  • Thanks. Just wanted to add if someone want to make the field required, this will not be enough. You should add this as well.: [hidden]="showSomething" [required]="!showSomething" – Ahmed Aziz Apr 08 '22 at 04:15
18

The accepted answer is correct in all means and I stumbled upon this thread after I couldn't get the Google Map render in one of my app components.

Now, if you are on a recent angular version i.e. 7+ of angular then you will have to deal with the following ViewChild declaration i.e.

@ViewChild(selector: string | Function | Type<any>, opts: {
read?: any;
static: boolean;
})

Now, the interesting part is the static value, which by definition says

  • static - True to resolve query results before change detection runs

Now for rendering a map, I used the following ,

@ViewChild('map', { static: true }) mapElement: any;
  map: google.maps.Map;
Prateek
  • 3,923
  • 6
  • 41
  • 79
  • I think i am facing what you said, but I keep receiving undefined, how do you reference mapElement? – Andres Felipe Jul 20 '20 at 16:29
  • @AndresFelipe for my input autocomplete I made like this: `@ViewChild('searchAddress', {static: true}) public address: ElementRef;` and in html `` hope it's helpful – MargeKh Feb 02 '21 at 12:58
  • This worked for me. @ViewChild('gantt_here', { static: true }) ganttContainer!: ElementRef; – Zaheer Baloch Jun 01 '21 at 20:49
12

I had a similar problem, but in my case I was trying to read the nativeElement inside the ngOnInit method:

@ViewChild('userNameInput') userNameInput: ElementRef<HTMLInputElement>;
...
ngOnInit(): void {
    this.userNameInput.nativeElement.focus();
}

I've changed to ngAfterViewInit and everything worked fine:

@ViewChild('userNameInput') userNameInput: ElementRef<HTMLInputElement>;
...
ngAfterViewInit(): void {
    this.userNameInput.nativeElement.focus();
}
Felipe Windmoller
  • 1,528
  • 1
  • 12
  • 24
11

This error occurs when you're trying to target an element that is wrapped in a condition.

So, here if I use ngIf in place of [hidden], it will give me TypeError: Cannot read property 'nativeElement' of undefined

So use [hidden], class.show or class.hide in place of *ngIf.

<button (click)="displayMap()" class="btn btn-primary">Display Map</button>

   <div [hidden]="!display">
      <div #mapContainer id="map">Content to render when condition is true.</div>
   </div>
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Tapas Vashi
  • 231
  • 2
  • 5
4

in my case i just checking undefied

` @ViewChild('myinput') myInputField: ElementRef;

ngAfterViewInit() {

if (this.myInputField !== undefined) {
  this.myInputField.nativeElement.focus();
}

}

Nikse
  • 375
  • 3
  • 12
3

Sometimes, this error occurs when you're trying to target an element that is wrapped in a condition, for example: <div *ngIf="canShow"> <p #target>Targeted Element</p></div>

In this code, if canShow is false on render, Angular won't be able to get that element as it won't be rendered, hence the error that comes up.

One of the solutions is to use a display: hidden on the element instead of the *ngIf so the element gets rendered but is hidden until your condition is fulfilled.

Read More over at Github

Zadat Olayinka
  • 441
  • 3
  • 8
0

What happens is when these elements are called before the DOM is loaded these kind of errors come up. Always use:

 window.onload = function(){
     this.keywordsInput.nativeElement.focus();
 }
PeterJ
  • 3,705
  • 28
  • 51
  • 71
Priyanka Arora
  • 409
  • 4
  • 10
0

remove *ngIf

in my case inside @ViewChild element I was using *ngIf like below.

<ul class="menu" *ngIf="isMenuOpen" #menu>
  <li *ngFor="let data of dropdownData" (click)="onClickOfVal(data)">
    {{ data }}
  </li>
</ul>

updated code

<ul class="menu" [style.display]="isMenuOpen ? 'block' : 'none'" #menu>
  <li *ngFor="let data of dropdownData" (click)="onClickOfVal(data)">
    {{ data }}
  </li>
</ul>
Aayush Bhattacharya
  • 1,628
  • 18
  • 17
-3

Initializing the Canvas like below works for TypeScript/Angular solutions.

const canvas = <HTMLCanvasElement> document.getElementById("htmlElemId"); 

const context = canvas.getContext("2d"); 
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
-5

it just simple :import this directory

import {Component, Directive, Input, ViewChild} from '@angular/core';
slfan
  • 8,950
  • 115
  • 65
  • 78