284

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?

Liam
  • 27,717
  • 28
  • 128
  • 190
shammelburg
  • 6,974
  • 7
  • 26
  • 34

8 Answers8

379

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();
  }
}
Muhammad Mabrouk
  • 606
  • 9
  • 16
rashfmnb
  • 9,959
  • 4
  • 33
  • 44
  • 11
    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. – Waleed Shahzaib Dec 29 '17 at 12:47
  • 1
    try this link https://angular.io/guide/component-interaction and comment the directives link – rashfmnb Dec 29 '17 at 13:04
  • 17
    How to make it work when there is multiple children of same class?? – Anandhu Ajayakumar May 15 '18 at 13:27
  • @rashfmnb "Declaration expected". An error is coming when I try to write @ViewChild('child') child:ChildCmp;in the component. Please help! And Also i cannot import the same in the directive it gives me error like "directive: (typeof EmployeeProfileC...' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'directive' does not exist in type 'Component'." – Trilok Pathak Oct 29 '18 at 08:50
  • 8
    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 – Bogdan D Jul 03 '20 at 08:46
  • 1
    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. – Paul Evans May 25 '23 at 20:39
  • 1
    The best solution that solved my problem. Thank you my friend! – AlexKh Aug 11 '23 at 10:10
131

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

Aljosha Koecher
  • 443
  • 6
  • 17
user6779899
  • 1,311
  • 1
  • 8
  • 2
  • 8
    It is an elegant solution, however it does not work properly in all cases, probably due Angular change detection not working from subscribe. – Alexei - check Codidact Jul 05 '18 at 08:57
  • 1
    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 Nov 19 '18 at 09:39
  • @shr any chance you can share your solution to pass an object with callback ? – Imad El Hitti Jan 28 '19 at 10:16
  • @ImadElHitti, added a sample solution as a separate answer, since code is not allowed within comments. – shr Jan 28 '19 at 10:55
  • 2
    This one is elegant solution, this should be the accepted answer, just change import method like import {Subject} from 'rxjs'; – VIKAS KOHLI Dec 19 '19 at 07:16
  • A subject is not necessarily required here because it is a one-way relationship, an EventEmitter would be sufficient for this job. – Berk Kurkcuoglu May 30 '20 at 18:55
  • Nice, I was looking for something like this, it's a _loosely coupling_ approach. However, the subject in child should be only an `Observable`, the child must not be able to emit events. – Bogdan D Jul 03 '20 at 08:49
  • This is indeed the best solution as ViewChild won't work with content projection, you'll have to use ContentChild instead and after a while it can be complex to know which use case, while Subject is consistant. – Shadoweb Nov 21 '21 at 09:25
84

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!"; 
  }
 }

RaidenF
  • 3,411
  • 4
  • 26
  • 42
Kaur A
  • 857
  • 6
  • 4
  • 4
    What is ChildVM in this line: @ViewChild(ChildComponent ) child: ChildVM; – Waleed Shahzaib Nov 22 '17 at 07:43
  • @WaleedShahzaib I think OP meant `ChildComponent` by `ChildVM` – Ajeet Shah May 18 '18 at 10:42
  • 1
    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! – tatsu Sep 18 '18 at 14:10
  • 4
    I am always getting Undefined value of "this.child" – Ambuj Khanna Jan 03 '19 at 05:15
  • I have the same problem as @AmbujKhanna – Kelvin Low Mar 08 '19 at 15:51
  • 2
    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. – tony Apr 03 '19 at 14:15
  • NullInjectorError: No provider for ChildComponent! – Mateen May 09 '22 at 06:47
19

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

Hemant Ramphul
  • 565
  • 5
  • 8
13

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
    }
}
  • Cannot find name `ChildComponent`. Do I need to import something else beside `ViewChild`? – Jeb50 Aug 07 '22 at 19:36
  • 1
    Solution to my own issue: `import {ChildComponent} from './whateverpath/child.component`. This is the easiest. Recommended! – Jeb50 Aug 08 '22 at 16:28
12

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 
        };
    }
 }
shr
  • 875
  • 10
  • 20
7

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.

lwairore
  • 624
  • 1
  • 7
  • 11
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.

Vibhor Dube
  • 4,173
  • 1
  • 22
  • 32