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

15,934

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.

Share:
15,934
Dheeraj Naik
Author by

Dheeraj Naik

Updated on June 26, 2022

Comments

  • Dheeraj Naik
    Dheeraj Naik almost 2 years

    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.

    • Pranav C Balan
      Pranav C Balan over 6 years
      template doesn't contain AdminComponent
    • Developer Thing
      Developer Thing over 6 years
      The error message is clear. You are referencing @ViewChild(AdminComponent) without having a reference in your header.monitor.component.html. You don't have <monitor-admin></monitor-admin> and you don't have appropriate template variable (#admin)
  • Nicholas Pesa
    Nicholas Pesa over 6 years
    I also believe you want to explicitly set your title variable to 'public'
  • Dheeraj Naik
    Dheeraj Naik over 6 years
    Yup I explicitly set the variable to public. Yea these both are parent components, I will use @Input property instead of ViewChild. But I do not want to use the view of the admin component, Just the properties of admin component. How can I achieve this ?
  • Nicholas Pesa
    Nicholas Pesa over 6 years
    I edited my answer above to explain how this can be achieved, but take note you should definitely do some more research on how components interact with data between them.
  • Dheeraj Naik
    Dheeraj Naik over 6 years
    I want to set the title value in admin component after button click event in admin.component.html and get that value in header component using @Input.
  • Nicholas Pesa
    Nicholas Pesa over 6 years
    I added another way to change that title on the fly using the rxjs/Subject and rxjs/Observable
  • Dheeraj Naik
    Dheeraj Naik over 6 years
    Using the final approach I was able to send data only when I injected admin view in the header.component.html. But, when I send data from the separate route of admin view, The header.component is not effected. How to send data with rxjs/subject that can effect data in other components on different routes. Note: I specified in @NgModule Providers and as dependency in both components.