Angular 2 @ViewChild not working. Cannot Read Property "title" of Undefined

Dheeraj Naik picture Dheeraj Naik · Nov 29, 2017 · Viewed 15k times · Source

I am unable to access the property of component even after importing with @ViewChild. Below is the code.

header.monitor.component.ts

import { AdminComponent } from 'admin/admin.component';
import { Component } from '@angular/core';
import { ViewChild, AfterViewInit } from '@angular/core';

@Component({
    selector: 'monitor-header',
    templateUrl: './header.monitor.component.html',
    styleUrls: ['./header.monitor.component.css']
})

export class HeaderMonitorComponent implements AfterViewInit {

    @ViewChild(AdminComponent) private admin: AdminComponent;

    private monitorTitle: string;

    ngAfterViewInit() {
        this.monitorTitle = this.admin.title;
    }
 }

header.monitor.component.html

<div class="text-center header h3">{{monitorTitle}}</div>

admin.component.ts

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

@Component({
  selector: 'monitor-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css']
})
export class AdminComponent {

  constructor() { }

  title = 'Header';

}

ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'title' of undefined TypeError: Cannot read property 'title' of undefined

Help me resolve this error.

Answer

Nicholas Pesa picture Nicholas Pesa · Nov 29, 2017

You should be checking for the @ViewChild in the AfterViewChecked instead of the AfterViewInit. Also you can bundle your @angular/core imports like so: import { Component, ViewChild, AfterViewInit } from '@angular/core';. Then instead of implementing AfterViewInit just implement AfterViewChecked.

Here is how it could look:

import { AdminComponent } from 'admin/admin.component';
import { Component, ViewChild, AfterViewChecked } from '@angular/core';

@Component({
    selector: 'monitor-header',
    templateUrl: './header.monitor.component.html',
    styleUrls: ['./header.monitor.component.css']
})

export class HeaderMonitorComponent implements AfterViewChecked {

    @ViewChild(AdminComponent) private admin: AdminComponent;

    private monitorTitle: string;

    ngAfterViewChecked() {
        this.monitorTitle = this.admin.title;
    }
 }

One question: Are these both parent components or will one of the components be a child of the other? The reason i ask is that you may want to look into a different method of passing that variable between components as this may be achieved with an @Input or using a service to store and set the header variable. I hope this can be of some help.

EDIT:

To answer your comment, you should create a service like this:

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

@Injectable()
export class HeaderService {

  _title: string;

  constructor() { }

  set title(title) {
    this._title = title;
  }
  get title() {
    return this._title;
  }

}

Then in your component import the service and get or set the variable:

import { AdminComponent } from 'admin/admin.component';
import { Component, ViewChild, AfterViewChecked } from '@angular/core';
import { HeaderService } from 'path/to/your/service';

@Component({
    selector: 'monitor-header',
    templateUrl: './header.monitor.component.html',
    styleUrls: ['./header.monitor.component.css']
})

export class HeaderMonitorComponent implements AfterViewChecked {

    @ViewChild(AdminComponent) private admin: AdminComponent;

    private monitorTitle: string;

    constructor(private headerService: HeaderService) {} // Import service here


    ngAfterViewChecked() {
        this.monitorTitle = this.headerService.title;
    }
 }

You just need to make sure you set the title in one of the components using this.headerService.title = 'yourTitle';

I am just not sure which component gets loaded first.

You should check out Angular Services here: https://angular.io/tutorial/toh-pt4

Also check out Angular Component Interaction here: https://angular.io/guide/component-interaction

EDIT #2:

Here is another way to subscribe to that title in your Service:

So here below I have created a Subject that is of type string, and a method that tells it here is the next string to hold and send.

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class HeaderService {

  public titleSubject: Subject<string> = new Subject<string>();

  constructor() { }

  setNextTitle(title) {
    this.titleSubject.next();
  }

}

Then in your HeaderMonitorComponent you would want to subscribe to that Subject like so:

import { AdminComponent } from 'admin/admin.component';
import { Component, OnInit ViewChild, AfterViewChecked } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HeaderService } from 'path/to/your/service';

@Component({
    selector: 'monitor-header',
    templateUrl: './header.monitor.component.html',
    styleUrls: ['./header.monitor.component.css']
})

export class HeaderMonitorComponent implements OnInit, AfterViewChecked {

    @ViewChild(AdminComponent) private admin: AdminComponent;

    private monitorTitle: string;
    titleObservable: Observable<string>;

    constructor(private headerService: HeaderService) {} // Import service here

    ngOnInit() {

       this.titleObservable = this.headerService.titleSubject.asObservable();
       this.titleObservable.subscribe((title) => {
          this.monitorTitle = title;
       });
    }

    ngAfterViewChecked() {
        this.monitorTitle = this.headerService.title;
    }
 }

Then in your AdminComponent, whenever the button is clicked, call this.headerService.setNextTitle(title) and the new title which you are subscribing to in the HeaderMonitorComponent will then be acknowledged and replace the current value of monitorTitle.

Just another quick way to handle the data passing through.