#ViewChild on child component [duplicate] - javascript

I need to get the child component DOM reference from parent component using angular 4, but i can't access child component DOM, please guide me how to achieve this.
parent.component.html
<child-component></child-component>
parent.component.ts
import { Component, Input, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
#Component({
selector: 'parent-component',
templateUrl: './parent.component.html'
})
export class ParentComponent implements AfterViewInit {
#ViewChild('tableBody') tableBody: ElementRef;
constructor(){
console.log(this.tableBody);//undefined
}
ngAfterViewInit() {
console.log(this.tableBody);//undefined
}
}
child.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'child-component',
templateUrl: './child.component.html'
})
export class ChildComponent {
}
child.component.html
<div>
<table border="1">
<th>Name</th>
<th>Age</th>
<tbody #tableBody>
<tr>
<td>ABCD</td>
<td>12</td>
</tr>
</tbody>
</table>
</div>

To expand on Sachila Ranawaka answer:
First you need <child-component #childComp></child-component>
In your parent component, instead of ElementRef it should be ChildComponent:
#Component({
selector: 'parent-component',
templateUrl: './parent.component.html'
})
export class ParentComponent implements AfterViewInit {
#ViewChild('childComp') childComp: ChildComponent;
constructor(){
}
ngAfterViewInit() {
console.log(this.childComp.tableBody);
}
}
For your child component:
#Component({
selector: 'child-component',
templateUrl: './child.component.html'
})
export class ChildComponent {
#ViewChild('tableBody') tableBody: ElementRef;
}

Adding to Sachila's and penleychan's good answers, you can reference a component with #ViewChild just by its component's name:
parent.component.html
<!-- You don't need to template reference variable on component -->
<child-component></child-component>
Then in parent.component.ts
import { ChildComponent } from './child-component-path';
#Component({
selector: 'parent-component',
templateUrl: './parent.component.html'
})
export class ParentComponent implements AfterViewInit {
#ViewChild(ChildComponent) childComp: ChildComponent;
constructor(){
}
ngAfterViewInit() {
console.log(this.childComp.tableBody);
}
}

Need to reference the tableBody from the parent component. So add it to he child-component and remove it from tbody
<child-component #tableBody></child-component>

just to add here , if you also need to change the DOM element attributes then once you have got access of child component Dom as demonstrated in #penleychan answer.
after that just set the color.
let parentdiv= this.childComp.tableBody.nativeElement;
parentdiv.style.backgroundColor='red';

Related

Angular - trying to use child component function in parent view but I'm gettting an error

When I use #ViewChild I get the error that the component is not defined.
When I use #ViewChildren I get the error that the function from that component is not a function.
I am new to using child components in Angular so I'm not sure why it's doing this when I do have the child component defined in the parent component and when it's clearly a function in the child component.
I don't want to have to define every function from the child in the parent or else what's even the point of using a separate component.
Child Component
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-mood',
templateUrl: './mood.component.html',
styleUrls: ['./mood.component.css']
})
export class MoodComponent implements OnInit {
moodColors = ['red', 'orange', 'grey', 'yellow', 'green'];
constructor() { }
ngOnInit(): void {
}
chooseMood() {
alert(this.moodColors);
}
}
Parent Component (Relavant Part of Version with "ERROR TypeError: ctx_r3.mood is undefined")
import { Component, OnInit, ViewChild, ViewChildren } from '#angular/core';
import { ViewEncapsulation } from '#angular/core';
import { MoodComponent } from '../mood/mood.component';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class CalendarComponent implements OnInit {
#ViewChild('mood') mood: MoodComponent = new MoodComponent;
Parent Component (Relavant Part of Version with "ERROR TypeError: ctx_r3.mood.chooseMood is not a function")
import { Component, OnInit, ViewChild, ViewChildren } from '#angular/core';
import { ViewEncapsulation } from '#angular/core';
import { MoodComponent } from '../mood/mood.component';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class CalendarComponent implements OnInit {
#ViewChildren('mood') mood: MoodComponent = new MoodComponent;
Parent View
<h2 (click)="mood.chooseMood()"></h2>
You don't explicitly initialize view children via new.
Just use:
#ViewChild('mood') mood : MoodComponent;
If that doesn't work post a Stackblitz example which I can edit to resolve the issue.
Also, using ViewChild is more of an exception in Angular, and your use of it points to a probable design issue. More likely you child component should emit via an Output to the parent.
Regarding outputs, you can do something like this - though it is hard to give a precise answer without deeper knowledge of what you are trying to achieve:
export class MoodComponent implements OnInit {
#Input() moodId: string;
#Output() chooseMood = new EventEmitter<string>();
moodClicked(){
this.chooseMood.emit(moodId);
}
}
export class CalendarComponent implements OnInit {
moodChosen(string: moodId){
console.log(moodId);
}
}
// Calendar template:
<app-mood
moodId="happy"
(chooseMood)="moodChosen($event)"
></app-mood>
1 - you have to use this code
#ViewChild('mood') mood : MoodComponent;
when you are using #ViewChildren it will return list of items with the 'mood' name then you have to use this code
mood.first.chooseMood() ;
its better use ViewChildren when there is ngIf in your element
2- no need new keyword for initialize mood variable
it would be fill after ngOnInit life cycle fires

Angular - How to invoke function from parent to child component

I have this situation: A parent component structured in this way:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class ParentComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
with this html (parent has an ng-content):
<div>
<ng-content></ng-content>
<button>Click to say Hello!!!</button>
</div>
and a child component like this:
import { Component, OnInit } from "#angular/core";
#Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
onSubmit() {
alert("hello");
}
constructor() {}
ngOnInit() {}
}
with this html:
<div>I'm child component</div>
From the parent component I want to click inside button to invoke onSubmit child function...is this possible?
<app-parent>
<app-child>
</app-child>
</app-parent>
This is a sample; I'm creating a modal that has default buttons: "CANCEL" and "SUCCESS". On the success button I need to invoke one function declared into the childrenComponent.
This is the stackblitz example: https://stackblitz.com/edit/angular-ivy-vuctg4
You can access like that
app.component.html
<hello name="{{ name }}"></hello>
<app-parent (clickActionCB)="clickActionCB($event)">
<app-child></app-child>
</app-parent>
app.component.ts
import { Component, VERSION, ViewChild } from "#angular/core";
import { ChildComponent } from "./child/child.component";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular " + VERSION.major;
#ViewChild(ChildComponent) childRef: ChildComponent;
clickActionCB(eventData) {
this.childRef.onSubmit() ;
}
}
parent.component.ts
import { Component, EventEmitter, OnInit, Output } from "#angular/core";
#Component({
selector: "app-parent",
templateUrl: "./parent.component.html",
styleUrls: ["./parent.component.css"]
})
export class ParentComponent implements OnInit {
constructor() {}
ngOnInit() {}
#Output() clickActionCB = new EventEmitter<string>();
clickAction() {
this.clickActionCB.emit();
}
}
parent.component.html
<div>
<ng-content></ng-content>
<button (click)="clickAction()">Click to say Hello!!!</button>
</div>
Live stackblitz URL
child.component.ts
import { Component, OnInit } from "#angular/core";
#Component({
selector: "app-child",
template: `
<div>I'm child component</div>
`
})
export class ChildComponent implements OnInit {
onSubmit() {
alert("hello");
}
constructor() {}
ngOnInit() {}
}
parent.component.ts
import { ChildComponent } from "../child/child.component";
#Component({
selector: "app-parent",
template: `
<div>
<ng-content></ng-content>
<button (click)="onClick()">Click to say Hello!!!</button>
</div>
`
})
export class ParentComponent implements OnInit {
#ContentChild(ChildComponent) childRef: ChildComponent;
constructor() {}
ngOnInit() {}
onClick() {
this.childRef.onSubmit();
}
}
https://stackblitz.com/edit/angular-ivy-mteusj?file=src%2Fapp%2Fparent%2Fparent.component.ts

How can I access an already transcluded ContentChild?

I have a angular component app-b that is used within a component app-a that is used in the app-component. The app-component has some content in app-a, app-a transcludes this with ng-content into app-b, app-b shows it with another ng-content - but how can I access this content within the component (and not it's template)?
I would think that ContentChild is the correct approach but appears to be wrong.
Example:
https://stackblitz.com/edit/angular-ddldwi
EDIT: Updated example
You cannot query by tag name with #ContentChild decorator. You can query either by template variable, component or directive selector.
app-a.component.html
<app-b>
<ng-content></ng-content>
<p #myContent>This is a content child for app-b.</p>
</app-b>
app-b.component.ts
import { Component, AfterContentInit, ContentChild } from '#angular/core';
#Component({
selector: 'app-b',
templateUrl: './b.component.html',
styleUrls: ['./b.component.css']
})
export class BComponent implements AfterContentInit {
#ContentChild('myContent') contentchild;
ngAfterContentInit() {
console.log(this.contentchild);
}
}
Live demo
I recommend sharing the data between components. For example, move your data (E.G. dummyName) into a service. Then add the service to each component (where you need the shared data).
Service:
import { Injectable } from '#angular/core';
#Injectable()
export class DataShareService {
public dummyName: String = 'one';
constructor() { }
}
Add the new service to app.module.ts:
providers: [DataShareService],
Child Component:
import { DataShareService } from './data-share.service';
import { Component } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
constructor(public ds: DataShareService) { }
toggle() {
this.ds.dummyName = this.ds.dummyName === 'one' ? 'two' : 'one';
}
}
Child Component template (html):
<p> {{ds.dummyName}}</p>
<button (click)="toggle()">Click Me to Toggle Value</button>
Parent Component:
import { Component, OnInit } from '#angular/core';
import { DataShareService } from './data-share.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(public ds: DataShareService) {}
displayNumber() {
return this.ds.dummyName === 'one' ? 1 : 2;
}
}
Parent Component template (html):
<p> this is the parent component:</p>
<div> value as string: {{ ds.dummyName }} </div>
<div> value as number: <span [textContent]="displayNumber()"></span></div>
<hr>
<p> this is the child component:</p>
<app-child></app-child>
Note! The child component toggle() function demonstrates how you can change the data. The parent component displayNumber() function demonstrates how to use the data, independent of it's display (I.E. as just pure data).
This appears to be impossible due to a bug in Angular:
https://github.com/angular/angular/issues/20810
Further reference:
https://www.reddit.com/r/Angular2/comments/8fb3ku/need_help_how_can_i_access_an_already_transcluded/

How to create nested components in Angular 4?

I have created a component called Parent and Child. I want to display all the UI of ChildComponent to my ParentComponent.
child.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {
testContent = 'child component content...';
constructor() { }
ngOnInit() {
}
}
child.component.html
<p>{{testContent}}</p>
parent.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss'],
directives: [ChildComponent]
})
export class AdminComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
parent.component.html
<div>
Lorem ipsum
<app-child></app-child>
</div>
I want to display the contents of child component inside parent component. However, I am encountering an error in Angular 4
"Object literal may only specify known properties, and 'directives'
does not exist in type 'Component'."
Do you have any idea what is the alternative property to add child component?
Remove directives from the parent component and add the child component to the module declarations array where the parent component lives.
Directives and pipes in #component are deprecated since angular RC6. Just remove it from the component.
#Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss']
})

Angular2 - Trying to add a component into a child component from parent

I am trying to add component dynamically into a child component from the parent component. I have injected the child component so that I can call its method in order to add component into the child component. Here is how I am doing it:
test.ts
import {Component, DynamicComponentLoader,ElementRef,AfterViewInit} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {ChildComponent} from './child_test.ts';
#Component({
selector: 'app',
template: '<div #element></div>',
providers: [ChildComponent,ElementRef]
})
class AppComponent implements AfterViewInit{
constructor(private _loader: DynamicComponentLoader, private _elementRef: ElementRef, private _childComponent: ChildComponent) {
}
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'element');
this._childComponent.addElement();
}
}
bootstrap(AppComponent);
child_test.ts
import {Component, ElementRef,DynamicComponentLoader} from 'angular2/core';
#Component({
selector: 'element',
template: '<div>Test Element</div>'
})
export class MyElement{}
#Component({
selector: "child-component",
template: "<div>Child Component</div>"
})
export class ChildComponent{
constructor(public _elementRef: ElementRef,private _loader: DynamicComponentLoader){}
public addElement(){
this._loader.loadNextToLocation(MyElement,this._elementRef);
}
};
When execute the test.ts I get the following error:
TypeError: Cannot read property 'getViewContainerRef' of undefined
Not sure if I fully understand your question but I think this is what you want
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'element')
.then(childComponent => childComponent.instance.addElement());
}
this._loader.loadIntoLocation(...) returns a Promise that completes with a reference to the added element.

Categories

Resources