简体   繁体   中英

Angular 6 @Viewchild is not working with lazy loading

Here is my code that gives error cannot read property title undefined.

Parent Component

import { Child } from './child.component';
@Component({
  selector: 'parent',
})
export class ParentComponet implements OnInit, AfterViewInit {
 constructor(){}

  @ViewChild(Child) child: Child;

  ngAfterViewInit(){
    console.log("check data", this.child.title)
  }
}

And Child Component is.

@Component({
      selector: 'child',
    })
    export class ChildComponet {

     public title = "hi"
     constructor(){}

    }

routing.module.ts is like

{
        path: "",
        component: ParentComponent,
        children: [
            {
                path: '/child',
                component: ChildComponent
            }
        ]
}

And Gives error is

ERROR TypeError: Cannot read property 'title' of undefined(…)

I think you are missing 'template' or 'templateUrl' in relevance to creating a Component

ParentComponent

import { ChildComponent } from './child.component';    // {ChildComponent} not {Child} as we are referencing it to the exported class of ChildComponent

@Component({
   selector: 'parent',
   template: `<child></child>`
})
export class ParentComponet implements OnInit, AfterViewInit {...}

ChildComponent

@Component({
  selector: 'child',
  template: `<h1>{{ title }}</h1>`
})
export class ChildComponent {...}       // Be sure to spell it right as yours were ChildComponet - missing 'n'

UPDATE as per the user's clarification on this thread

Had added a Stackblitz Demo for your reference (Check the console)

If you want to access the ChildComponent that is rendered under the Parent Component's <router-outlet> you can do so by utilizing (activate) supported property of router-outlet :

A router outlet will emit an activate event any time a new component is being instantiated

Angular Docs

ParentComponent's Template

@Component({
   selector: 'parent',
   template: `<router-outlet (activate)="onActivate($event)"></router-outlet>`
})
export class ParentComponent {

    onActivate(event): void {
        console.log(event);         // Sample Output when you visit ChildComponent url
                                    // ChildComponent {title: "hi"}

        console.log(event.title);   // 'hi' 
    }

}

The result will differ based on the visited page under your parent's children

If you visit Child1Component you will get its instance Child1Component {title: "hi"}

If you visit Child2Component you will get its instance Child2Component {name: "Angular"}

These results will then be reflected on your ParentComponent's onActivate(event) console for you to access

That's not how it's supposed to work. You'll be only able to get the ChildComponent in your ParentComponent ONLY if you have the <app-child></app-child> tag in your ParentComponent Template.

Something like this:

...

<app-child></app-child>

...

But since you're using child routing, and the ChildComponent will load on the router-outlet of your ParentComponent you won't have access to that using ViewChild

PS: You'll only have access to it inside ngAfterViewInit as ViewChild can only be considered safe to have instantiated after the View has loaded:

import { Component, OnInit, ViewChild } from '@angular/core';
import { ChildComponent } from '../child/child.component';
...

@Component({...})
export class ParentComponent implements OnInit {

  @ViewChild(ChildComponent) childComponent: ChildComponent;

  ...

  ngAfterViewInit() {
    console.log(this.childComponent);
  }

}

Here's a Working Sample StackBlitz for your ref that illustrates your scenario in both the cases.

PS: To get the ChildComponent properties in your ParentComponent , with Routing, you'll have to either use a SharedService or you'll have to pass the ChildProperty in the route as a QueryParam and read it in your ParentComponent using the ActivatedRoute

UPDATE:

Sharing Data using Route Query Params:

Although this won't make much sense, but in your ChildComponent , you can have a Link that would route the user to the ChildComponent with the title property passed as a queryParam . Something like this:

<a 
  [routerLink]="['/child']" 
  [queryParams]="{title: title}">
  Go To Child Route With Query Params
</a>

And in your ParentComponent have access to it using ActivatedRoute like this:

...
import { ActivatedRoute } from '@angular/router';
...

@Component({...})
export class ParentComponent implements OnInit {

  ...

  constructor(
    private route: ActivatedRoute,
    ...
  ) { }

  ngOnInit() {
    this.route.queryParams.subscribe(queryParams => {
      console.log('queryParams[`title`]', queryParams['title']);
    });
    ...
  }

  ...

}

Using a SharedService

Just create a SharedService with a private BehaviorSubject that would be exposed as an Observable by calling the asObservable method on it. It's value can be set by exposing a method( setChildProperty ) that will essentially call the next method with the updated childProperty value :

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable()
export class SharedService {

  private childProperty: BehaviorSubject<string> = new BehaviorSubject<string>(null);
  childProperty$: Observable<string> = this.childProperty.asObservable();

  constructor() { }

  setChildProperty(childProperty) {
    this.childProperty.next(childProperty);
  }

}

You can then inject it both in your ParentComponent and in your ChildComponent :

In ChildComponent set the value:

import { Component, OnInit } from '@angular/core';
import { SharedService } from '../shared.service';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {

  public title = "hi"

  constructor(private sharedService: SharedService) { }

  ngOnInit() {
    this.sharedService.setChildProperty(this.title);
  }

}

And in your ParentComponent get the value:

...
import { SharedService } from '../shared.service';

@Component({...})
export class ParentComponent implements OnInit {

  ...

  constructor(
    ...,
    private sharedService: SharedService
  ) { }

  ngOnInit() {
    ...
    this.sharedService.childProperty$.subscribe(
      childProperty => console.log('Got the Child Property from the Shared Service as: ', childProperty)
    );
  }

  ...

}

确保在parent.component.html模板中添加了<child></child>标记。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM