THE PROBLEM
So I have two Angular components, a parent and a child. The parent passes a custom template to the child component, 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.component, 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.component.html",
styleUrls: ["./app.component.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.component (Child)
import {
Component,
ElementRef,
Input,
TemplateRef,
ViewChild
} from "#angular/core";
#Component({
selector: "child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.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.
ViewChild doesn't seem to pick up a rendered template - probably because it's not part of the components 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 component, and access it using ContentChildren. You subscribe to the ContentChildren QueryList for changes, which will update when the DOM element becomes 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.component.css"]
})
export class AppComponent {}
#Component({
selector: "child",
template: `
<ng-container *ngTemplateOutlet="customTemplate"> </ng-container>
`,
styleUrls: ["./child.component.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'
});
}
}
Related
I am trying to call view child of a child component from parent and getting undefined in the console.
see the image also see the stack blaze for the same
https://stackblitz.com/edit/angular-ivy-k4m2hp?file=src%2Fapp%2Fhello.component.ts
import { Component, Input, OnInit, ViewChild } from '#angular/core';
import { TestChildComponent } from './test-child/test-child.component';
#Component({
selector: 'hello',
template: `<h1>this is Hello component {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent {
#Input() name: string;
#ViewChild('TestChildComponent') testChildComponent: TestChildComponent
ngOnInit() {
console.log('calling ngOninit in hello component');
console.log('going to call test child instance this.TestChildComponent.ngOninit')
console.log(this.testChildComponent);
}
}
Please help to get the child component
this.testChildComponent
So that i can call ngOnInit of child from parent.
this.testChildComponent.ngOnInit()
ViewChild element ref can be accessed in ngAfterViewInit() cycle the earliest.
Angular doc says we should use the child component injected by ViewChild in ngAfterViewInit. But sometimes you even can’t get it in ngAfterViewInit. The reason is, the child component is not created yet when AfterViewInit hook runs. For such a case you would have to wait more (using a setTimeout would work but it's a bad idea). Other option would be to have the child emit something to the parent, letting it know the child has been rendered and then the parent can query it.
But your case is that sibling HelloComponent wants to query sibling TestChildComponent it can't do that. TestChildComponent is just not in the scope for HelloComponent. Easiest solution would be to query TestChildComponent from the parent AppComponent.
You should also add #TestChildComponent to access the ref.
<app-test-child #TestChildComponent childname="{{ name }}"></app-test-child>
Working example: https://stackblitz.com/edit/angular-ivy-j5ceat?file=src%2Fapp%2Fapp.component.html
if you set your viewChild { static: true } you will be able to access it in ngOnInit
but in your sample the issue is due totestChildComponent is a child of app.component and not hello.component
<app-test-child childname="{{ name }}" #test></app-test-child>
app.component.ts
import { Component, VERSION, ViewChild } from '#angular/core';
import { TestChildComponent } from './test-child/test-child.component';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
name = 'this is from app compoenent';
#ViewChild('test', { static: true }) testChildComponent: TestChildComponent;
ngOnInit() {
console.log('calling ngOninit in app component');
console.log(this.testChildComponent);
}
}
If you want to access testChildComponent from hello.component you will have to send it the component as an input for sample
following a working sample of accessing testChildComponent
https://stackblitz.com/edit/angular-ivy-tvwukg?file=src%2Fapp%2Fapp.component.html
Hope someone can enlighten me.
Problem
I need to get a reference to a directive placed inside an inner component.
I'm using #ViewChild targeting Directive class, with {static:true}since it doesnt have to wait for state changes and use it later on lifecicle when the user clicks a button.
#ViewChild(DirectiveClass, {static:true}) childRef : DirectiveClass;
Expected
To have directive reference in childRef instance variable when the event happens.
Actual
childRef is undefined
I did research for similar problems and all seemed to be because ref was inside a *ngIf and should be {static: false}; or because the ref was intended to be used before it was fetched(before ngAfterViewInit hook). This is not the case, this case is fair simplier and yet cant get whats wrong! :/
Reproduction
Situation
so, I got two components and a directive. Lets call them ParentComponent and SonComponent. Directive is applied in son component, so lets call it SonDirective.
Basically this is the structure.
<app-parent>
<app-son> </app-son> //<- Directive inside
</app-parent>
Code
//parent.component.ts
#Component({
selector: 'app-parent',
template: `
<div>
<h2> parent </h2>
<app-son></app-son>
</div>
`,
})
export class AppParentComponent implements AfterViewInit{
#ViewChild(AppSonDirective,{static: true}) childRef : AppSonDirective;
constructor() {}
ngAfterViewInit() {
console.log("childRef:",this.childRef)
}
}
// son.component.ts
#Component({
selector: 'app-son',
template: `
<div appSonDirective>
<p> son <p>
</div>
`,
})
export class AppSonComponent {
constructor() {}
}
//son.directive.ts
#Directive({
selector: '[appSonDirective]'
})
export class AppSonDirective {
constructor(){}
}
Note: if i move the view child ref to the son component it can be accessed. The issue seems to be at parent component (?)
Anyway... here is the reproducion code(it has some logs to know whats going on)
Any thoughts will be helpfull.
Thanks in advance! :)
Problem is that #ViewChild only works around the component DOM but don't have access to the DOM of its children components, that would break encapsulation between components. In this case appSonDirective is declared in AppSonComponent DOM but because you're trying to access it from AppParentComponent it returns undefined because AppParentComponent can't access AppSonComponent DOM. It could work if you had something like this:
<app-parent>
<app-son appSonDirective></app-son>
</app-parent>
One solution is to expose the directive as a property of you child component. Something like:
#Component({
selector: 'app-son',
template: `
<div appSonDirective>
<p> son <p>
</div>
`,
})
export class AppSonComponent {
#ViewChild(AppSonDirective,{static: true}) childRef : AppSonDirective;
constructor() {}
}
and then in AppParentComponent
#Component({
selector: 'app-parent',
template: `
<div>
<h2> parent </h2>
<app-son></app-son>
</div>
`,
})
export class AppParentComponent implements AfterViewInit{
#ViewChild(AppSonComponent,{static: true}) son : AppSonComponent;
constructor() {}
ngAfterViewInit() {
console.log("childRef:",this.son.childRef)
}
}
In Angular I want to project (Transclude) a nativeElement to another component without using innerHTML.
Currently I'm trying to do this using ngComponentOutlet using a child component that has an ng-content.
I want to load the component and send html data to it all rendered so I dont need to use innerHTML to compile it to HTML.
Reason for this is, innerHTML looses the bindings.
This is my attempt:
My Parent Component:
import { Component, ContentChildren, QueryList } from '#angular/core';
import { TableRowComponent } from '../table-row/table-row.component';
import { TableCellComponent } from '../table-cell/table-cell.component';
#Component({
selector: 'ls-table-body',
template: `
<ng-container>
<tr ls-table-row *ngFor="let row of rows">
<ng-container *ngFor="let cell of cells">
<ng-container *ngComponentOutlet="tableCellComponent; content: [[cell.elem.nativeElement]]"></ng-container>
</ng-container>
</tr>
</ng-container>
`,
styleUrls: ['./table-body.component.scss']
})
export class TableBodyComponent {
#ContentChildren(TableRowComponent) rows: QueryList<TableRowComponent>;
#ContentChildren(TableCellComponent) cells: QueryList<TableCellComponent>;
public tableCellComponentComponent = TableCellComponent;
}
My Child Component called TableCellComponent:
import { Component, HostBinding, Input, ContentChildren, ViewEncapsulation, QueryList } from '#angular/core';
#Component({
selector: '[ls-table-cell]',
template: `
<ng-content></ng-content>
`,
styleUrls: ['./table-cell.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TableCellComponent {
#HostBinding() #Input('class') classList = 'ls-table-cell';
#ContentChildren(TableCellComponent) cells: QueryList<TableCellComponent>;
#Input() span: number;
#Input() data: any;
}
Outcome I'm looking for:
I'm looking to compile the HTML - as I cannot use innerHTML due to the bindings getting removed - so I've optted for the ngComponentOutlet solution using ng-content - as I believe that compiles my HTML I pass through using cell.elem.nativeElement.
I'm also open to alternatives
I'm creating a 3d "card flip" using angular 2. A parent 'card-flip' component contains a nested 'card-flip-front' and 'card-flip-back' component.
<card-flip card-flip-id="demo-1" class="grid_col-6">
<card-flip-front class="card">
<div class="card__inner">
Card Front
</div>
</card-flip-front>
<card-flip-back class="card">
<div class="card__inner">
Card Back
</div>
</card-flip-back>
</card-flip>
I would like to create a "clone" of the card-flip-front component with content projection and data-binding in tact. The "clone" would be used for animating and the "original" would remain in it's original position hidden. That way I have a reference of where the "clone" should animate to when it returns to the original position (even if the user scrolls or resizes the window).
The main challenge I'm facing is that I need the content within the ng-content tag to also be projected in the "clone". The problem being that the first ng-content tag will be used by Angular for content projection and additional, unlabeled ng-content tags will be empty (which I know is the expected behavior).
One might ask, "why not just create a dumb, static copy of the element in the DOM?". I would like to avoid this so that nested components and data bindings that inject data (thereby modifying the dimensions of the element) will continue to work.
Here's my work so far which creates an instance of the CardFlipFront component via ComponentFactory to serve as the "clone" and simply inserts the innerHTML of the "original" CardFlipFront.
import {
Component,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
ContentChild,
Inject,
Input,
OnInit,
ViewChild,
ViewContainerRef
} from '#angular/core';
import { CardFlipFrontComponent } from './card-flip-front.component';
import { CardFlipBackComponent } from './card-flip-back.component';
import { CardFlipService } from './card-flip.service';
#Component({
selector: 'card-flip',
templateUrl: './card-flip.component.html',
styleUrls: ['./card-flip.component.css'],
entryComponents: [
CardFlipFrontComponent
]
})
export class CardFlipComponent implements OnInit {
#Input('card-flip-id') public id: string;
#ContentChild(CardFlipFrontComponent) private front: CardFlipFrontComponent;
#ContentChild(CardFlipBackComponent) private back: CardFlipBackComponent;
#ViewChild('frontCloneContainer', { read: ViewContainerRef }) private frontCloneContainer: ViewContainerRef;
private frontComponentRef: ComponentFactory<CardFlipFrontComponent>;
private frontClone: ComponentRef<CardFlipFrontComponent>;
constructor(
#Inject(CardFlipService) private _cardFlipService: CardFlipService,
private _componentFactoryResolver: ComponentFactoryResolver
) {
this.frontComponentRef = this._componentFactoryResolver.resolveComponentFactory(CardFlipFrontComponent);
}
ngOnInit() {
this._cardFlipService.register(this.id);
}
ngAfterViewInit() {
// Create a card-flip-front component instance to serve as a "clone"
this.frontClone = this.frontCloneContainer.createComponent(this.frontComponentRef);
// Copy the innerHTML of the "original" into the "clone"
this.frontClone.instance.el.nativeElement.innerHTML = this.front.el.nativeElement.innerHTML;
}
ngOnDestroy() {
this.frontClone.destroy();
}
}
<ng-content select="card-flip-front"></ng-content>
<ng-container #frontCloneContainer></ng-container>
<ng-content select="card-flip-back"></ng-content>
import {
Component,
ElementRef,
HostBinding,
Input,
OnInit,
Renderer
} from '#angular/core';
#Component({
selector: 'card-flip-front',
templateUrl: './card-flip-front.component.html',
styleUrls: ['./card-flip-front.component.css']
})
export class CardFlipFrontComponent implements OnInit {
constructor(private _el: ElementRef, private _renderer: Renderer) { }
public get el(): ElementRef {
return this._el;
}
public get renderer(): Renderer {
return this._renderer;
}
ngOnInit() { }
}
<ng-content></ng-content>
UPDATE:
Ok, so after reading about some similar challenges and the github issue here, I tried the following.
<ng-template #frontTemplate>
<ng-content select="card-flip-front"></ng-content>
</ng-template>
<ng-container *ngIf="isOpen == true" #front1>
<ng-container *ngTemplateOutlet="frontTemplate"></ng-container>
</ng-container>
<ng-container *ngIf="isOpen == false" #front2>
<ng-container *ngTemplateOutlet="frontTemplate"></ng-container>
</ng-container>
<ng-content select="card-flip-back"></ng-content>
Basically, we can get around the single projection issue with ng-content by placing it within a template and using two ng-container tags with an *ngIf statement that will only show one instance of the template based on a class property isOpen.
This doesn't solve the entire issue though because only one container will be rendered at any given time. So, I can't get the current position of "original" to figure out where to animate the "clone" during the return animation described above.
I think you can have an intermediary component <card-flip-content> inside <card-flip> template which is duplicated and which receives the <ng-content> of the <card-flip>.
Something like:
#Component(
selector = 'card-flip',
template = `
<card-flip-content #theOne>
<ng-content />
</card-flip-content>
<card-flip-content #theClone>
<ng-content />
</card-flip-content>
`)
Then bind data as needed to #theOne and #theClone and animate only #theClone.
This way can have #Input and #Output thus leaving the actions of one component to be interpreted by the parent in order to act on the other component.
Would that work?
How can I access the "content" of a component from within the component class itself?
I would like to do something like this:
<upper>my text to transform to upper case</upper>
How can I get the content or the upper tag within my component like I would use #Input for attributes?
#Component({
selector: 'upper',
template: `<ng-content></ng-content>`
})
export class UpperComponent {
#Input
content: String;
}
PS: I know I could use pipes for the upper case transformation, this is only an example, I don't want to create an upper component, just know how to access the component's content from with the component class.
If you want to get a reference to a component of the transcluded content, you can use:
#Component({
selector: 'upper',
template: `<ng-content></ng-content>`
})
export class UpperComponent {
#ContentChild(SomeComponent) content: SomeComponent;
}
If you wrap <ng-content> then you can access access to the transcluded content like
#Component({
selector: 'upper',
template: `
<div #contentWrapper>
<ng-content></ng-content>
</div>`
})
export class UpperComponent {
#ViewChild('contentWrapper') content: ElementRef;
ngAfterViewInit() {
console.debug(this.content.nativeElement);
}
}
You need to leverage the #ContentChild decorator for this.
#Component({
selector: 'upper',
template: `<ng-content></ng-content>`
})
export class UpperComponent {
#Input
content: String;
#ContentChild(...)
element: any;
}
Edit
I investigated a bit more your issue and it's not possible to use #ContentChild here since you don't have a root inner DOM element.
You need to leverage the DOM directly. Here is a working solution:
#Component({
selector: 'upper',
template: `<ng-content></ng-content>`
})
export class UpperComponent {
constructor(private elt:ElementRef, private renderer:Renderer) {
}
ngAfterViewInit() {
var textNode = this.elt.nativeElement.childNodes[0];
var textInput = textNode.nodeValue;
this.renderer.setText(textNode, textInput.toUpperCase());
}
}
See this plunkr for more details: https://plnkr.co/edit/KBxWOnyvLovboGWDGfat?p=preview
https://angular.io/api/core/ContentChildren
class SomeDir implements AfterContentInit {
#ContentChildren(ChildDirective) contentChildren : QueryList<ChildDirective>;
ngAfterContentInit() {
// contentChildren is set
}
}
Note that if you do console.log(contentChildren), it will only work on ngAfterContentInit or a later event.
Good morning,
I've been doing a little research on this topic because something similar has happened to me in my project. What I have discovered is that there are two decorators that can help you for this solution: ViewChildren and ContentChildren. The difference mainly according to what I have found in the network is that ViewChildren accesses the interior of the component and ContentChildren accesses the DOM or the component itself.
To access the upper element from the ng-content you must change the upper element leaving it like this:
<upper #upper>my text to transform to upper case</upper>
And then simply to access the interior (In the component that has the ng-content):
#ViewChildren('upper') upper: QueryList<ElementRef>
And to access the component in general (In the component that has the ng-content):
#ContentChildren('upper') upper: QueryList<ElementRef>
Where did you get the information from: https://netbasal.com/understanding-viewchildren-contentchildren-and-querylist-in-angular-896b0c689f6e
You can also use TemplateRef, which was what worked me in the end because I was using a service to initialize my component.
In your-component.component.html
<!-- Modal Component & Content -->
<ng-template #modal>
... modal content here ...
</ng-template>
In your-component.component.ts
#ViewChild('modal') modalContentRef!: TemplateRef<any>;
... pass your modalContentRef into your child ...
... For me here, the service was the middleman - [Not shown] ...
In modal.component.html
<div class="modal-container">
<!-- Content will be rendered here in the ng-container -->
<ng-container *ngTemplateOutlet="modalContentRef">
</ng-container>
</div>
In modal.component.ts
#Input() modalContentRef: TemplateRef<any> | null = null;
This how I ended up solving the problem with a TemplateRef, since ng-content could not be used since a service was creating the modal