BeniaminoBaggins
BeniaminoBaggins

Reputation: 12433

@viewChild not working - cannot read property nativeElement of undefined

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>

Upvotes: 98

Views: 289851

Answers (12)

Nikse
Nikse

Reputation: 375

in my case i just checking undefied

@ViewChild('myinput') myInputField: ElementRef;

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

Upvotes: 4

Rupam Pal
Rupam Pal

Reputation: 1

<p #contentParagraph>
      <strong *ngIf="serverElement.type === 'server'" style="color: red">{{
        serverElement.content
      }}</strong>
      <em *ngIf="serverElement.type === 'blueprint'">{{
        serverElement.content
      }}</em>
        {{ serverElement.content }}
      </strong>
    </p>

When I was using my local reference on ngIf statement it was giving an error cannot read property native element of undefined.

To rectify this problem we have to replace ngIf with any property binding then we can access our nativelement via ViewChild or ContentChild in another component.

       <p #contentParagraph> 
       <strong
        [ngStyle]="{
          color: serverElement.type === 'server' ? 'red' : 'blue'
        }"
      >
        {{ serverElement.content }}
      </strong>
    </p>

Upvotes: 0

Aayush Bhattacharya
Aayush Bhattacharya

Reputation: 1934

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>

Upvotes: 0

Felipe Windmoller
Felipe Windmoller

Reputation: 1753

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

Upvotes: 14

Tapas Vashi
Tapas Vashi

Reputation: 311

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>

Upvotes: 13

Prateek
Prateek

Reputation: 4013

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;

Upvotes: 18

Zadat Olayinka
Zadat Olayinka

Reputation: 471

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

Upvotes: 3

luke neeley
luke neeley

Reputation: 47

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

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

const context = canvas.getContext("2d"); 

Upvotes: -3

Priyanka Arora
Priyanka Arora

Reputation: 457

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

Upvotes: -1

Munaf Wadiwala
Munaf Wadiwala

Reputation: 7

it just simple :import this directory

import {Component, Directive, Input, ViewChild} from '@angular/core';

Upvotes: -5

user2023861
user2023861

Reputation: 8208

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>

Upvotes: 97

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657308

@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

Upvotes: 105

Related Questions