208

I am currently using Angular 2. Usually we use @Output() addTab = new EventEmitter<any>(); and then addTab.emit() to emit an event to parent component.

Is there any way we can do it vice cersa, from parent to child?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
code1
  • 8,351
  • 8
  • 27
  • 31
  • 4
    Just change the value of an expression passed as Input to the child component, and the child component will get it (and be informed by ngOnChanges). You could also emit an event using a shared service that has an Observable. – JB Nizet May 18 '17 at 16:36

7 Answers7

414

Using RxJs, you can declare a Subject in your parent component and pass it as Observable to child component, child component just need to subscribe to this Observable.

Parent-Component

eventsSubject: Subject<void> = new Subject<void>();

emitEventToChild() {
  this.eventsSubject.next();
}

Parent-HTML

<child [events]="eventsSubject.asObservable()"> </child>    

Child-Component

private eventsSubscription: Subscription;

@Input() events: Observable<void>;

ngOnInit(){
  this.eventsSubscription = this.events.subscribe(() => doSomething());
}

ngOnDestroy() {
  this.eventsSubscription.unsubscribe();
}
Yuri
  • 4,254
  • 1
  • 29
  • 46
BlueNC
  • 4,304
  • 1
  • 10
  • 10
  • 37
    Also, data can be passed along with the event using this approach. Like,`this.eventsSubject.next({data});` in the parent, then `this.events.subscribe(({data}) => doSomething(data));` in the child. – vince Jun 29 '18 at 01:37
  • 3
    Probably a beginner question here: Why convert `eventsSubject` to an Observable instead of just subscribing to it as a Subject? – Justin Morgan - On strike Nov 21 '18 at 17:25
  • 25
    Converting eventsSubject to an Observable, prevent the child-component to call next(). – BlueNC Dec 02 '18 at 22:47
  • 1
    in parent component eventsSubject should be public. – L. Heider Sep 26 '19 at 14:44
  • 1
    is this best practice? –  Dec 16 '19 at 06:11
  • 1
    What if the child component needs to subscribe to more than 1 event? What is the syntax for that? ` ` – ErnestoE Jan 30 '20 at 15:48
  • 1
    @ErnestoE just name the `events` variable different than `events`. Maybe a more speaking variable name related to it's actual purpose. This way it is also easier for you to read what the event is for – con Mar 02 '20 at 10:15
  • 5
    Thanks for this solution, IT WORKS but I'm getting an error in the console: " core.js:6185 ERROR TypeError: Cannot read property 'subscribe' of undefined " The error is pointing to the events.subscribe() in the ngOnInit of the child component: " this.eventsSubscription = this.events.subscribe(() => doSomething()); " I'm using the versions: "@angular/cli": "~9.1.0" and "rxjs": "~6.5.4" – EduardoInTech May 09 '20 at 03:28
  • @Eduardo check your parent component html template, make sure "events" attribute bind correctly – BlueNC May 12 '20 at 23:40
  • It's generally agreed that in Angular [you should use `EventEmitter` not `Subject`](https://stackoverflow.com/questions/34376854/delegation-eventemitter-or-observable-in-angular) – Liam May 19 '20 at 12:54
  • 1
    I am quite surprised that the answer got so many likes. Does it really a good practise to pass events into child component? Which problems does it solve? Why not to use just pass data and track it on changes or use some shared service? – maskalek Jul 27 '20 at 12:49
  • I understand in part the @maskalek comment. I use something like this thinking it this way: "don't see it in terms of "Parent->Child" coms but in terms of a reusable component that delegates the responsibility in any host/user may have of knowing the exact event source." – nilsandrey Oct 02 '20 at 16:24
  • You can use a "BehaviourSubject" instead of "Subject" to ensure the observer always give values, including initialization. – nilsandrey Oct 02 '20 at 16:26
  • This solution works, but if you use ngOnChanges, it triggers it a lot of times whiout reason, so it looks like it's a bad pattern – Nacho Oct 26 '20 at 20:56
  • y que es doSomething() , sale error , espera que esté declaro como una function . gracias. – César Rodríguez Reyes Nov 19 '21 at 16:46
132

As far as I know, there are 2 standard ways you can do that.

1. @Input

Whenever the data in the parent changes, the child gets notified about this in the ngOnChanges method. The child can act on it. This is the standard way of interacting with a child.

// Parent-Component
public inputToChild: Object;
        
// Parent-HTML
<child [data]="inputToChild"> </child>       
        
//Child-Component
@Input() data;

ngOnChanges(changes: { [property: string]: SimpleChange }) {
    // Extract changes to the input property by its name
    let change: SimpleChange = changes['data']; 

    // Whenever the data in the parent changes, this method gets triggered
    // You can act on the changes here. You will have both the previous
    // value and the  current value here.
}
  1. Shared service concept

Creating a service and using an observable in the shared service. The child subscribes to it and whenever there is a change, the child will be notified. This is also a popular method. When you want to send something other than the data you pass as the input, this can be used.

// SharedService
subject: Subject<Object> = new Subject<Object>();

// Parent-Component
constructor(sharedService: SharedService)
this.sharedService.subject.next(data);

// Child-Component
constructor(sharedService: SharedService) {
this.sharedService.subject.subscribe((data) => {
    // Whenever the parent emits using the next method,
    // you can receive the data in here and act on it.
});
Hannes Schneidermayer
  • 4,729
  • 2
  • 28
  • 32
prajwal gonsalves
  • 1,330
  • 1
  • 8
  • 8
  • I tried first method and it worked fine till some point and after that I got an error message stating The value got changed now, earlier it's false and now it's true. It did not give me any further explanation apart from this. – code1 May 19 '17 at 13:54
  • 2
    Given you mean a ExpressionChangedAfterItHasBeenCheckedError, this error will not be thrown in production mode. – user1337 Apr 04 '18 at 15:42
  • ` ` should probably be ` ` to get changes – pmc Feb 27 '19 at 00:05
  • 1
    When using the shared service, the service should be in the metadata header of the component class and not as a module. Module services are singletons, and thus global scope. Any variables stored there will linger and be global, likely causing defects and making bugs hard to discover. Good for configuration, bad for most other things. – TamusJRoyce Nov 09 '21 at 02:34
  • What's the difference between using ngOnChanges and a setter on the input prop? Or are those two entirely different use cases? – ChrisGS Nov 15 '22 at 15:44
  • there is a caveat with `onChanges` approach that if you fire the same value twice, it will be considered single because `onChanges` won't be fired as `@Input` has not changed yet. – Waleed Ahmad Mar 21 '23 at 07:39
69

In a parent component you can use @ViewChild() to access child component's method/variable.

@Component({
  selector: 'app-number-parent',
  templateUrl: './number-parent.component.html'
})
export class NumberParentComponent {
    @ViewChild(NumberComponent)
    private numberComponent: NumberComponent;
    increase() {
       this.numberComponent.increaseByOne();
    }
    decrease() {
       this.numberComponent.decreaseByOne();
    }
} 

Update:

Angular 8 onwards -

@ViewChild(NumberComponent, { static: false })
Hardik Patel
  • 3,868
  • 1
  • 23
  • 37
  • 11
    This seems cleaner than working with observables. Drawback is child has to be in view. If the child is loaded with routing for example, this will fail as `numberComponent` will be `undefined`. – Shy Agam Mar 07 '19 at 19:54
  • 4
    is this a good practice? i agree with the fact that's cleaner than observables, but i'm doubting about manipulate the child's variables from the parent. Anyway, it worked so nice for my needs, Thanks! – Takatalvi Aug 13 '19 at 14:38
  • 2
    This is a good tip. Looks like @ViewChild is changed in Angular 8, or at least I had to specify options of the ViewChild. I used this in my case: @ViewChild(Other, { static: false }) private otherComponent: Other; – Tore Aurstad Dec 12 '19 at 17:09
  • { static: false } is default option in Angular 9+. Which means it's no longer necessary to be specified. – BladeMaster May 05 '22 at 08:15
  • @BladeMaster Yes. You are right – Hardik Patel May 05 '22 at 08:30
  • This is a good solution when you have one child to "warn" of the event. One can still use ViewChildren or accumulates those object in the parent of course. The observable approach becomes interesting when you have heterogeneous children needing the event. – Jorgu Jul 26 '22 at 12:01
  • This is a brilliant solution, especially if you just need to check state of the child component, and not manipulate it. Amazing! – SDekov Jan 22 '23 at 20:21
11

Use the @Input() decorator in your child component to allow the parent to bind to this input.

In the child component you declare it as is :

@Input() myInputName: myType

To bind a property from parent to a child you must add in you template the binding brackets and the name of your input between them.

Example :

<my-child-component [myChildInputName]="myParentVar"></my-child-component>

But beware, objects are passed as a reference, so if the object is updated in the child the parent's var will be too updated. This might lead to some unwanted behaviour sometime. With primary types the value is copied.

To go further read this :

Docs : https://angular.io/docs/ts/latest/cookbook/component-communication.html

Noki
  • 383
  • 1
  • 9
2

None of the previous solutions worked for me because I had a Nested Recursive Child Component. So I used the following approach by using the OnChanges function.

Parent Component

buttonToggle: boolean = false;

buttonClick(): void {
  this.buttonToggle = !this.buttonToggle;
}

Parent HTML

<appNestedChildItemComponent [buttonTrigger]="buttonToggle"></appNestedChildItemComponent>

Recursive Nested Child Component

export class NestedChildItemComponent implements OnInit, OnChanges {

  @Input() buttonTrigger: boolean;
  buttonToggle: boolean = false;

  ngOnInit() {
    this.buttonToggle= buttonToggle;
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes['buttonTrigger'] && changes['buttonTrigger']?.previousValue != changes['buttonTrigger']?.currentValue) {
      this.buttonToggle= !this.buttonToggle;
      // Do Something triggered by the parent button.
    }
  }
}
nesterenes
  • 184
  • 2
  • 17
1

Within the parent, you can reference the child using @ViewChild. When needed (i.e. when the event would be fired), you can just execute a method in the child from the parent using the @ViewChild reference.

Paul Evans
  • 1,436
  • 2
  • 10
  • 13
0

On the same concept of shared services: one can also use a local store with property management. Parent(s) will update the state in the store and child(ren) may subsribe(s) to the store itself.

This approach makes it easier for many-to-many event/data/info transfers and can be applied the original question.

Drawback: heavier Advantage: more robust to big app evolutions

One example of store management library I would recommend: https://github.com/ngneat/elf

Jorgu
  • 137
  • 1
  • 11