Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 viewchild not working - cannot read property nativeElement of undefined

Tags:

angular

I have this component called multiselect.component and I access one of its html elements (<select id="category-select">) in the .ts file.

multiselect.component.html:

<select id="category-select" multiple class="form-control">
    <option value="1">Meat</option>
    <option value="2">Dairy</option>
    <option value="3">Confectionary</option>
    <option value="4">Dessert</option>
    <option value="7">Baking</option>
    <option value="6">Grocers</option>
    <option value="5">Restaurants</option>
    <option value="8">Condiments</option>
    <option value="9">beverages</option>
</select>
<div class="form-control-icon" id="keywords-icon"></div>

multiselect.component.ts:

import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'multiselect',
    templateUrl: 'app/shared/multiselect.component.html',
    styleUrls: [ 'app/shared/multiselect.component.css' ]
})
export class MultiselectComponent implements OnInit {
    items = 'one two three'.split(' ');
    selectedCategories;
    allSelected;
    numberOfCategories = 9;

    ngOnInit() {
        this.selectedCategories = [];
        (<any>$("#category-select")).multiselect({
            buttonWidth: '100%',
            buttonContainer: '<div style="height: 34px;" />',
            buttonClass: 'none',
            nonSelectedText: "select categories",
            nSelectedText: ' categories',
            allSelectedText: "all categories",
            selectAllNumber: false,
            includeSelectAllOption: true,
            disableIfEmpty: true,
            onSelectAll: () => {
                this.allSelected = true;
            },
            onInitialized: () => {

            },
            onChange: (option, checked) => {
                this.changed();
            }
        });
    }

    changed() {
        this.selectedCategories = [];
        var self = this;

        $('#category-select option:selected').each(function () {
            self.selectedCategories.push(
                <any>($(this).text(), $(this).val())
            )
        });
        console.log(this.selectedCategories);
        if (this.selectedCategories.length < this.numberOfCategories) {
            this.allSelected = false;
        }
    }
}

I think that the way I access it is bad practice by using jquery. I think it can be accessed with @viewChild instead.

So I'm trying to change it like this (I am first just getting rid of the first jquery element accessor then will do the rest later):

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

@Component({
    selector: 'multiselect',
    templateUrl: 'app/shared/multiselect.component.html',
    styleUrls: [ 'app/shared/multiselect.component.css' ]
})
export class MultiselectComponent implements OnInit {
    items = 'one two three'.split(' ');
    selectedCategories;
    allSelected;
    numberOfCategories = 9;

    @ViewChild('category-select') select;

    ngOnInit() {
        this.selectedCategories = [];
        this.select.nativeElement.multiselect({
            buttonWidth: '100%',
            buttonContainer: '<div style="height: 34px;" />',
            buttonClass: 'none',
            nonSelectedText: "select categories",
            nSelectedText: ' categories',
            allSelectedText: "all categories",
            selectAllNumber: false,
            includeSelectAllOption: true,
            disableIfEmpty: true,
            onSelectAll: () => {
                this.allSelected = true;
            },
            onInitialized: () => {

            },
            onChange: (option, checked) => {
                this.changed();
            }
        });
    }

    changed() {
        this.selectedCategories = [];
        var self = this;

        $('#category-select option:selected').each(function () {
            self.selectedCategories.push(
                <any>($(this).text(), $(this).val())
            )
        });
        console.log(this.selectedCategories);
        if (this.selectedCategories.length < this.numberOfCategories) {
            this.allSelected = false;
        }
    }
}

I'm getting this console error:

> EXCEPTION: Error: Uncaught (in promise): EXCEPTION: Error in app/find-page/find-form.component.html:0:56
ORIGINAL EXCEPTION: TypeError: Cannot read property 'nativeElement' of undefined
ORIGINAL STACKTRACE:
TypeError: Cannot read property 'nativeElement' of undefined
    at MultiselectComponent.System.register.context_1.execute.MultiselectComponent.ngOnInit (http://localhost:3000/js/app/shared/Multiselect.component.js:29:32)
    at DebugAppView._View_FindFormComponent0.detectChangesInternal (FindFormComponent.ngfactory.js:761:90)
    at DebugAppView.AppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12586:18)
    at DebugAppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12691:48)
    at DebugAppView.AppView.detectViewChildrenChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12612:23)
    at DebugAppView._View_FindPageComponent0.detectChangesInternal (FindPageComponent.ngfactory.js:41:8)
    at DebugAppView.AppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12586:18)
    at DebugAppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12691:48)
    at DebugAppView.AppView.detectViewChildrenChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12612:23)
    at DebugAppView.AppView.detectChangesInternal (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12597:18)
ERROR CONTEXT:
[object Object]

Why am I getting that error?

After changing to AfterViewInit, I'm now getting this error:

platform-browser.umd.js:937 Error: Uncaught (in promise): EXCEPTION: Error in app/find-page/find-form.component.html:13:30
ORIGINAL EXCEPTION: TypeError: Cannot read property 'length' of undefined
ORIGINAL STACKTRACE:
TypeError: Cannot read property 'length' of undefined
    at DebugAppView._View_FindFormComponent0.detectChangesInternal (FindFormComponent.ngfactory.js:890:72)
    at DebugAppView.AppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12586:18)
    at DebugAppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12691:48)
    at DebugAppView.AppView.detectViewChildrenChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12612:23)
    at DebugAppView._View_FindPageComponent0.detectChangesInternal (FindPageComponent.ngfactory.js:41:8)
    at DebugAppView.AppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12586:18)
    at DebugAppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12691:48)
    at DebugAppView.AppView.detectViewChildrenChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12612:23)
    at DebugAppView.AppView.detectChangesInternal (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12597:18)
    at DebugAppView.AppView.detectChanges (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:12586:18)
ERROR CONTEXT:
[object Object]
    at resolvePromise (http://localhost:3000/node_modules/zone.js/dist/zone.js:538:32)
    at http://localhost:3000/node_modules/zone.js/dist/zone.js:515:14
    at ZoneDelegate.invoke (http://localhost:3000/node_modules/zone.js/dist/zone.js:323:29)
    at Object.onInvoke (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:9245:45)
    at ZoneDelegate.invoke (http://localhost:3000/node_modules/zone.js/dist/zone.js:322:35)
    at Zone.run (http://localhost:3000/node_modules/zone.js/dist/zone.js:216:44)
    at http://localhost:3000/node_modules/zone.js/dist/zone.js:571:58
    at ZoneDelegate.invokeTask (http://localhost:3000/node_modules/zone.js/dist/zone.js:356:38)
    at Object.onInvokeTask (http://localhost:3000/node_modules/@angular/core//bundles/core.umd.js:9236:45)
    at ZoneDelegate.invokeTask (http://localhost:3000/node_modules/zone.js/dist/zone.js:355:43)

The error seems to be in FindFormComponent.

FindFormComponent is just the form that has the multiselect (a form to fill out which will then find products).

Here is the relevant html from the FindFormComponent that utilises length:

<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>
like image 406
BeniaminoBaggins Avatar asked Aug 23 '16 06:08

BeniaminoBaggins


People also ask

What is nativeElement in angular?

According to the official docs. Angular ElementRef is a wrapper around a native element inside of a View. It's simply a class that wraps native DOM elements in the browser and allows you to work with the DOM by providing the nativeElement object which exposes all the methods and properties of the native elements.

What is @ViewChild?

@ViewChild is a local component template querying mechanism, that cannot see the internals of its child components. By injecting references directly into our component class, we can easily write any coordination logic that involves multiple elements of the template.

How do I declare ElementRef?

To manipulate the DOM using the ElementRef , we need to get the reference to the DOM element in the component/directive. Create a template reference variable for the element in the component/directive. Use the template variable to inject the element into component class using the ViewChild or ViewChildren.

How do you use ViewChildren?

Descriptionlink. Use to get the QueryList of elements or directives from the view DOM. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value. View queries are set before the ngAfterViewInit callback is called.


1 Answers

You need to change

ngOnInit() {
    this.selectedCategories = [];
    this.select.nativeElement.multiselect({

to

ngAfterViewInit() {
    this.selectedCategories = [];
    this.select.nativeElement.multiselect({

The nativeElement isn't available before the view is initialized.

like image 50
Günter Zöchbauer Avatar answered Sep 20 '22 01:09

Günter Zöchbauer