Angular2 component: impossible to emit an @Output EventEmitter inside ngOnDestroy?
Asked Answered
T

2

1

[angular2 rc1]

Is it possible to have a component like this:

export class MyComp {
  @Output() myEvent = new EventEmitter(false)

  ngOnDestroy() {
    this.myEvent.emit('ngOnDestroy hook');
  }
}

And catch it in the parent:

<myComp (myEvent)="test($event)"></myComp>

It seems to be impossible but I would like to understand why?

I know I can use a service to walk through.

Plunker here

Tizzy answered 3/6, 2016 at 8:39 Comment(1)
Possible duplicate of Angular2 ngOnDestroy, emit eventPossessory
P
6

A service is a good solution, but you wanted to understand why:

A TL/DR explanation:

The subscription is unsubscribed for you, by angular before ngOnDestroy() is called. This automation happens because your subscribed on a template, angular parses the template and knows enough to do so.

A more detailed explanation:

Angular generates a factory for each component, this process is called Code generation and the factory function created by angular returns an instance of a unique View type created according to the metadata of your component, which inherits the AppView class.

The whole lifecycle of a component instance is managed by the view. For example, change detection and of course lifecycle hooks.

Before the ngOnDestroy lifecycle hook is called (by the internal view of your component) an internal destruction function is called, it does some cleaning for you. One of cleaning operations is unsubscribing from all events such as click, mouse move and custom events which are @Output emitters defined in child components.

This is done statically, which means that the code generator knows about the event subscription and so it adds hardcoded logic to remove it. Its an important thing, this saves a lot of boilerplate code and prevent memory leaks - the framework cleans up for you.

It's also important to note that angular can do it because it has a lot of metadata about what's going on. It knows that its a subscriptions because it parses the Template and find an event expression (e.g: (click)="something()") and also because of the @Output decorator in MyComp

You can see the logic quite clearly in the AppView class - LINK Every view inherits from AppView and implements (among other things) a function called destroyInternal. The order is that on destruction destroyLocal() is called (defined on AppView), destroyLocal will clean up all subscriptions and disposable items and then it will call destroyInternal, destroyInternal will then call ngOnDestroy() on your component's instance.

A Solution:

Now, services might be a good solution but if you understand what's going on you can solve this without the help of an external service, it very simple. As we now know, angular will unsubscribe from subscriptions registered in a template, if we register manually (i.e: using code) we will be able to fire the events.

While simple, it has some boilerplate since we need to:

  • We need to get a reference to our child component, we will use ViewChild
  • We need to subscribe to the event on our child component instance but we can do it only after the ngAfterViewInit lifecycle hook is fired.
  • We need to unsubscribe once we're done, this can be done in several ways, the best way is to complete the EventEmitter - we will do it in the component that defines the EventEmitter simply by call myEvent.complete(). It will dispose everything for us.

Here is the destroy function:

ngOnDestroy() {
  this.myEvent.emit('ngOnDestroy hook');
  this.myEvent.complete();
}

And here is the MyApp component:

export class MyApp {
  displayComp = true

  @ViewChild('mycomp', {read: MyComp}) myComp: MyComp;
  constructor() {}

  ngAfterViewInit() {
    this.myComp.myEvent.subscribe( () => alert('myApp > receive event via MANUAL Subscription'));
  }

  test(e) {
    alert('myApp > receive event');
  }
}

Here is a working plunker - LINK

UPDATE: I just noticed a pending PR for this issue in the angular repo, it's 12 days now still not merged, it takes time to merge community contribution so lets hope it will land in the next RC.

Palladous answered 7/7, 2016 at 16:22 Comment(0)
E
3

This PR should fix this problem. It changes the behavior that ngOnDestroy() is called before the events are detached.

https://github.com/angular/angular/pull/9946

Engrain answered 23/7, 2016 at 19:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.