316

I have created a child component which has a method I want to invoke.

When I invoke this method it only fires the console.log() line, it will not set the test property??

Below is the quick start Angular app with my changes.

Parent

import { Component } from '@angular/core';
import { NotifyComponent }  from './notify.component';

@Component({
    selector: 'my-app',
    template:
    `
    <button (click)="submit()">Call Child Component Method</button>
    `
})
export class AppComponent {
    private notify: NotifyComponent;

    constructor() { 
      this.notify = new NotifyComponent();
    }

    submit(): void {
        // execute child component method
        notify.callMethod();
    }
}

Child

import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'notify',
    template: '<h3>Notify {{test}}</h3>'
})
export class NotifyComponent implements OnInit {
   test:string; 
   constructor() { }

    ngOnInit() { }

    callMethod(): void {
        console.log('successfully executed.');
        this.test = 'Me';
    }
}

How can I set the test property as well?

2

9 Answers 9

416

You can do this by using @ViewChild for more info check this link

With type selector

child component

@Component({
  selector: 'child-cmp',
  template: '<p>child</p>'
})
class ChildCmp {
  doSomething() {}
}

parent component

@Component({
  selector: 'some-cmp',
  template: '<child-cmp></child-cmp>',
  directives: [ChildCmp]
})
class SomeCmp {

  @ViewChild(ChildCmp) child:ChildCmp;

  ngAfterViewInit() {
    // child is set
    this.child.doSomething();
  }
}

With string selector

child component

@Component({
  selector: 'child-cmp',
  template: '<p>child</p>'
})
class ChildCmp {
  doSomething() {}
}

parent component

@Component({
  selector: 'some-cmp',
  template: '<child-cmp #child></child-cmp>',
  directives: [ChildCmp]
})
class SomeCmp {

  @ViewChild('child') child:ChildCmp;

  ngAfterViewInit() {
    // child is set
    this.child.doSomething();
  }
}
Sign up to request clarification or add additional context in comments.

8 Comments

I followed your approach, but I'm getting error while using directives: [ChildCmp], The error says: directives' does not exist in type 'Component'. I've googled it and found directives is deprecated in rc5. So how to handle it on the newer version. Please help.
try this link angular.io/guide/component-interaction and comment the directives link
How to make it work when there is multiple children of same class??
This a correct answer, but it produces tightly coupled components. A better pattern is to use Input properties: an observable to which the child reacts by calling its own internal function. See user6779899 's answer
A gotcha here is if the child component is in an *ngIf. When the *ngIf condition is satisfied, the type of the ViewChild is ElementRef rather than "ChildCmp" or whatever. And as an ElementRef, you can't call the method.
|
150

I think most easy way is using Subject. In below example code, the child will be notified each time 'tellChild()' is called.

Parent.component.ts

import {Subject} from 'rxjs/Subject';
...
export class ParentComp {
  changingValue: Subject<boolean> = new Subject();
        
  tellChild() {
    this.changingValue.next(true);
  }
}

Parent.component.html

<my-comp [changing]="changingValue"></my-comp>

Child.component.ts

...
export class ChildComp implements OnInit{
  @Input() changing: Subject<boolean>;
  
  ngOnInit(){
    this.changing.subscribe(v => { 
      console.log('value is changing', v);
    });
  }
}

Working sample on Stackblitz

8 Comments

It is an elegant solution, however it does not work properly in all cases, probably due Angular change detection not working from subscribe.
Neat ! For simpler cases, you can avoid the Subject/Subscribe overhead by passing an object that has a callback method to the child. Similar to the above, the child overrides the callback to receive indications from the parent.
@shr any chance you can share your solution to pass an object with callback ?
@ImadElHitti, added a sample solution as a separate answer, since code is not allowed within comments.
This one is elegant solution, this should be the accepted answer, just change import method like import {Subject} from 'rxjs';
|
89

This Worked for me ! For Angular 2 , Call child component method in parent component

Parent.component.ts

    import { Component, OnInit, ViewChild } from '@angular/core';
    import { ChildComponent } from '../child/child'; 
    @Component({ 
               selector: 'parent-app', 
               template: `<child-cmp></child-cmp>` 
              }) 
    export class parentComponent implements OnInit{ 
        @ViewChild(ChildComponent ) child: ChildComponent ; 

        ngOnInit() { 
           this.child.ChildTestCmp(); } 
}

Child.component.ts

import { Component } from '@angular/core';
@Component({ 
  selector: 'child-cmp', 
  template: `<h2> Show Child Component</h2><br/><p> {{test }}</p> ` 
})
export class ChildComponent {
  test: string;
  ChildTestCmp() 
  { 
    this.test = "I am child component!"; 
  }
 }

7 Comments

What is ChildVM in this line: @ViewChild(ChildComponent ) child: ChildVM;
@WaleedShahzaib I think OP meant ChildComponent by ChildVM
I thought this would create a separate instance of the component but it actually calls the function from your instance with it's variables in the current state of that component, holy cow! this method is way better than the first answer!
I am always getting Undefined value of "this.child"
My guess for 'this.child' being undefined is that either ViewChild is pointing at something that doesn't exist in the template, or you are trying to access it too early in the lifecycle, e.g. in the constructor.
|
24

parent.component.html

<app-child #childComponent></app-child>

parent.component.ts

@Component({
    selector: 'app-parent',
    templateUrl: './app-parent.component.html',
    styleUrls: ['./app-parent.component.scss']
})
export class ParentComponent {
    @ViewChild('childComponent', {static: false}) childComponent: ChildComponent;

    anyMethod(): void {
        childComponent.updateData() // updateData is a child method
    }
}

child.component.ts

@Component({
    selector: 'app-child',
    templateUrl: './app-child.component.html',
    styleUrls: ['./app-child.component.scss']
})
export class ChildComponent {
    updateData(): void {
      // Method code goes here
    }
}

2 Comments

Cannot find name ChildComponent. Do I need to import something else beside ViewChild?
Solution to my own issue: import {ChildComponent} from './whateverpath/child.component. This is the easiest. Recommended!
22

Angular – Call Child Component’s Method in Parent Component’s Template

You have ParentComponent and ChildComponent that looks like this.

parent.component.html

enter image description here

parent.component.ts

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

@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css']
})
export class ParentComponent {
  constructor() {
  }
}

child.component.html

<p>
  This is child
</p>

child.component.ts

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

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

  doSomething() {
    console.log('do something');
  }
}

When serve, it looks like this:

enter image description here

When user focus on ParentComponent’s input element, you want to call ChildComponent’s doSomething() method.

Simply do this:

  1. Give app-child selector in parent.component.html a DOM variable name (prefix with # – hashtag), in this case we call it appChild.
  2. Assign expression value (of the method you want to call) to input element’s focus event.

enter image description here

The result:

enter image description here

2 Comments

OK but we also want to do it programmatically using ts
For use from within the component: @ViewChild('appChild', { static: false }) appChild: ElementRef<HTMLElement>; and later use this.appChild.doSomething()
16

user6779899's answer is neat and more generic However, based on the request by Imad El Hitti, a light weight solution is proposed here. This can be used when a child component is tightly connected to one parent only.

Parent.component.ts

export class Notifier {
    valueChanged: (data: number) => void = (d: number) => { };
}

export class Parent {
    notifyObj = new Notifier();
    tellChild(newValue: number) {
        this.notifyObj.valueChanged(newValue); // inform child
    }
}

Parent.component.html

<my-child-comp [notify]="notifyObj"></my-child-comp>

Child.component.ts

export class ChildComp implements OnInit{
    @Input() notify = new Notifier(); // create object to satisfy typescript
    ngOnInit(){
      this.notify.valueChanged = (d: number) => {
            console.log(`Parent has notified changes to ${d}`);
            // do something with the new value 
        };
    }
 }

Comments

8

Consider the following example,

import import { AfterViewInit, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';

@Component({
    selector: 'app-countdown-parent-vc',
    templateUrl: 'app-countdown-parent-vc.html',
    styleUrl: [app-countdown-parent-vc.css]
})
export class CreateCategoryComponent implements OnInit, AfterViewInit {
    @ViewChild(CountdownTimerComponent, {static: false}) private timerComponent: CountdownTimerComponent;
    ngAfterViewInit() {
        this.timerComponent.startTimer();
    }

    submitNewCategory(){
        this.ngAfterViewInit();
    }
}

Read more about @ViewChild here.

3 Comments

this is the best answer.
There was no need to explicitly call the ngAfterViewInit(). it will be called automatically if implemented on the class
There's a need to explicitly call the ngAfterViewInit() because we explicitly need to tell Angular to call method startTimer(), which is in component CountdownTimerComponent
0

I had an exact situation where the Parent-component had a Select element in a form and on submit, I needed to call the relevant Child-Component's method according to the selected value from the select element.

Parent.HTML:

<form (ngSubmit)='selX' [formGroup]="xSelForm">
    <select formControlName="xSelector">
      ...
    </select>
<button type="submit">Submit</button>
</form>
<child [selectedX]="selectedX"></child>

Parent.TS:

selX(){
  this.selectedX = this.xSelForm.value['xSelector'];
}

Child.TS:

export class ChildComponent implements OnChanges {
  @Input() public selectedX;

  //ngOnChanges will execute if there is a change in the value of selectedX which has been passed to child as an @Input.

  ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
    this.childFunction();
  }
  childFunction(){ }
}

Hope this helps.

Comments

0

I used EventEmitter for this.

parent.component.ts

@Component({
  selector: "app-parent",
  template: '<app-child listener="notify"></app-child>' +
            '<button (click)="notify.emit()">Call child function</button>',
  //...
})
export class ParentComponent {
  notify: EventEmitter<void> = new EventEmitter<void>()
}

child.component.ts

@Component({
  selector: "app-child",
  template: "<i>Child component text</i>",
  //...
})
export class ChildComponent {
  #listener: EventEmitter<void> = new EventEmitter<void>()
  @Input()
  set listener(emitter: EventEmitter<void>) {
    this.#listener.unsubscribe()
    this.#listener = emitter
    this.#listener.subscribe(this.childFunction.bind(this))
  }

  childFunction() {
    console.log("Child function called")
  }
}

Good parts of this approach:

  • reliable - works even if you change EventEmitter (value of notify property in ParentComponent) later in code
  • keeps component decoupled

Bad part:

  • little bit more coding is needed compared to other solutions

If you need to pass some data, replace void (in EventEmitter<void>) with desired type.

If you need to pass more than 1 parameter, then I would suggest to create interface and pass data as single object containing all parameters you need to pass to ChildComponent. Quicker (and dirtier) solution to this would be to set type as any and pass whatever object you want.

Example with passing object as parameter

parent.component.ts

@Component({
  selector: "app-parent",
  template: '<app-child listener="notify"></app-child>' +
            '<button (click)="callChild()">Call child function</button>',
  //...
})
export class ParentComponent {
  notify: EventEmitter<any> = new EventEmitter<any>()

  callChild() {
    this.notify.emit({title: 'Parent call', id: 1})
  }
}

child.component.ts

@Component({
  selector: "app-child",
  template: "<i>Child component text</i>",
  //...
})
export class ChildComponent {
  #listener: EventEmitter<any> = new EventEmitter<any>()
  @Input()
  set listener(emitter: EventEmitter<any>) {
    this.#listener.unsubscribe()
    this.#listener = emitter
    this.#listener.subscribe(this.childFunction.bind(this))
  }

  childFunction(data: any) {
    console.log("Child function called")
    console.log(data)
  }
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.