最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I use @ViewChild with an external ng-template (Angular 11) - Stack Overflow

programmeradmin1浏览0评论

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'
    });
  }
}
发布评论

评论列表(0)

  1. 暂无评论