admin管理员组

文章数量:1305917

THE PROBLEM

So I have two Angular ponents, a parent and a child. The parent passes a custom template to the child ponent, which then hydrates the template with its own data using ngTemplateOutlet.

This works well for the most part. Unfortunately, I run into issues when trying to access the DOM elements of this parent template from the child.

If I try to access <div #container></div> from the default child template using @ViewChild('container',{static: false}), it gets the element without issue. When I do the same using the custom template passed in by appponent, I get the error "cannot read property 'nativeElement' of undefined".

What else do I have to do to access the DOM of my template?

Here's a Stackblitz

App.Component (Parent)

import { Component } from "@angular/core";

@Component({
  selector: "my-app",
  templateUrl: "./appponent.html",
  styleUrls: ["./appponent.css"]
})
export class AppComponent {}
<child [customTemplate]="parentTemplate"></child>

<ng-template #parentTemplate let-context="context">

    <div #container>HELLO FROM CONTAINER</div>
    <button (click)="context.toggleShow()">Toggle Display</button>
    <div *ngIf="context.canShow">Container contains the text: {{context.getContainerText()}}</div>
</ng-template>

THE PROBLEM

So I have two Angular ponents, a parent and a child. The parent passes a custom template to the child ponent, which then hydrates the template with its own data using ngTemplateOutlet.

This works well for the most part. Unfortunately, I run into issues when trying to access the DOM elements of this parent template from the child.

If I try to access <div #container></div> from the default child template using @ViewChild('container',{static: false}), it gets the element without issue. When I do the same using the custom template passed in by app.ponent, I get the error "cannot read property 'nativeElement' of undefined".

What else do I have to do to access the DOM of my template?

Here's a Stackblitz

App.Component (Parent)

import { Component } from "@angular/core";

@Component({
  selector: "my-app",
  templateUrl: "./app.ponent.html",
  styleUrls: ["./app.ponent.css"]
})
export class AppComponent {}
<child [customTemplate]="parentTemplate"></child>

<ng-template #parentTemplate let-context="context">

    <div #container>HELLO FROM CONTAINER</div>
    <button (click)="context.toggleShow()">Toggle Display</button>
    <div *ngIf="context.canShow">Container contains the text: {{context.getContainerText()}}</div>
</ng-template>

child.ponent (Child)

import {
  Component,
  ElementRef,
  Input,
  TemplateRef,
  ViewChild
} from "@angular/core";

@Component({
  selector: "child",
  templateUrl: "./child.ponent.html",
  styleUrls: ["./child.ponent.css"]
})
export class ChildComponent {
  @Input() public customTemplate!: TemplateRef<HTMLElement>;
  @ViewChild("container", { static: false })
  public readonly containerRef!: ElementRef;

  templateContext = { context: this };
  canShow: boolean = false;
  

  toggleShow() {
    this.canShow = !this.canShow;
  }
  getContainerText() {
    return this.containerRef.nativeElement.textContent;
  }
}
<ng-container *ngTemplateOutlet="customTemplate || defaultTemplate; context: templateContext">
</ng-container>

<ng-template #defaultTemplate>
    <div #container>GOODBYE FROM CONTAINER</div>
    <button (click)="toggleShow()">Toggle Display</button>
    <div *ngIf="canShow">Container contains the text: {{getContainerText()}}</div>
</ng-template>

MY QUESTION

How do I use @ViewChild to access this div from an outside template that updates with any changes in the DOM? (Note: Removing the *ngIf is NOT an option for this project)

What's causing this? Are there any lifecycle methods that I can use to remedy this issue?

MY HUNCH I'm guessing that ViewChild is being called BEFORE the DOM updates with its new template and I need to setup a listener for DOM changes. I tried this and failed so I'd really appreciate some wisdom on how best to proceed. Thanks in advance :)

EDIT: This solution needs to properly display <div #container></div> regardless of whether you're passing in a custom template or using the default one.

Share Improve this question edited Feb 20, 2021 at 19:18 treycrossley asked Feb 20, 2021 at 4:59 treycrossleytreycrossley 1431 gold badge1 silver badge8 bronze badges 0
Add a ment  | 

1 Answer 1

Reset to default 6

ViewChild doesn't seem to pick up a rendered template - probably because it's not part of the ponents template initially. It's not a timing or lifecycle issue, it's just never available as a ViewChild

An approach that does work is to pass in the template as content to the child ponent, and access it using ContentChildren. You subscribe to the ContentChildren QueryList for changes, which will update when the DOM element bees rendered

You can then access the nativeElement (the div). If you wanted you could add listeners here to the DOM element, and trigger cd.detectChanges afterwards, but that would be a bit unusual. It would probably be better to handle DOM changes in the parent element, and pass the required values down to the child using regular @Input on the child

@Component({
  selector: "my-app",
  template: `
    <child>
      <ng-template #parentTemplate let-context="context">
        <div #container>Parent Template</div>
      </ng-template>
    </child>
  `,
  styleUrls: ["./app.ponent.css"]
})
export class AppComponent {}
@Component({
  selector: "child",
  template: `
    <ng-container *ngTemplateOutlet="customTemplate"> </ng-container>
  `,
  styleUrls: ["./child.ponent.css"]
})
export class ChildComponent implements AfterContentInit {
  @ContentChild("parentTemplate")
  customTemplate: TemplateRef<any>;

  @ContentChildren("container")
  containerList: QueryList<HTMLElement>;

  ngAfterContentInit() {
    this.containerList.changes.subscribe(list => {
      console.log(list.first.nativeElement.innerText);
      // prints 'Parent Template'
    });
  }
}

本文标签: javascriptHow do I use ViewChild with an external ngtemplate (Angular 11)Stack Overflow