Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2: "Global" lifecycle hooks?

Tags:

angular

TL;DR

We are building an app with Angular 2 and would like to register a "global" ngOnInit and ngOnDestroy function. With "global" I mean that the function is executed for every component, without needing to be explicitly implemented for every component. Is this possible?

Detailed

Some (but not all) of our components need to register something in a global service once they are loaded (e.g. ngOnInit) and unregister it again, once they are unloaded (e.g. ngOnDestroy). Here are two approaches I can think of:

  • Implement this logic in every component which needs to do so (in ngOnInit and ngOnDestroy).
  • Implement this logic in a base class which calls abstract methods implemented by the sub classes which specify, what needs to be registered/unregistered.

Both approaches aren't really satisfying:

  • In the first I need to implement the whole logic over and over again. Yeah, I know, I could put the "boiler plate"-code in a helper class or something. But it seems to be slightly implicit, "hidden" in the regular Angular lifecycle. Having a speaking name (e.g. interface/base class) seems more explicit.
  • In the second all classes need to extend the same, common base class. But what if they should also extend some other base class (multiple inheritance)?

That's why i came up with following idea:

Why not replace the above mentioned abstract class with an interface that can be implemented by all the required components. I would then register a global function which is executed on every ngOnInit and ngOnDestroy of all the components (if possible - e.g. in an module, routing, etc.?). In the function I would check if the component implements the interface and if it does, then call the appropriate function to get the type specific stuff to be registered.

My questions

  • Can Angular 2 do this? I.e. can a "global lifecycle hook" (or something like that) be registered?
  • Or is this a totally wrong approach anyway?
  • Any other/better ideas?
like image 995
PzYon Avatar asked Sep 09 '16 21:09

PzYon


1 Answers

Forcing the behaviour for the entire app wouldn't be a good idea, this affects third-party components as well for starters.

Boilerplate code can be moved into concrete base class. There are solutions for JS/TS multiple inheritance, e.g. @mixin, see also TypeScript guide.

Since base class methods are fixed, class mixin can be expressed as a simplified decorator:

class CustomLifecycle implements OnInit, OnDestroy  {
  constructor(
    public originalNgOnInit: Function,
    public originalNgOnDestroy: Function
  ) {}

  ngOnInit() {
    ...
    if (typeof this.originalNgOnInit === 'function') {
      this.originalNgOnInit();
    }
  }

  ngOnDestroy() {
    ...
    if (typeof this.originalNgOnDestroy === 'function') {
      this.originalNgOnDestroy ();
    }
  }
}

function Lifecycled() {
  return function (target: Function) {
    const customLifecycle = new CustomLifecycle(
      target.prototype.ngOnInit,
      target.prototype.ngOnDestroy
    );

    target.prototype.ngOnInit = customLifecycle.ngOnInit;
    target.prototype.ngOnDestroy = customLifecycle.ngOnDestroy;
  }
}

And it can be used like

@Component({ ... })
@Lifecycled()
class SomeComponent { .... }

The implementation is limited to ngOnInit, etc. prototype methods, arrow members require a constructor to be patched.

like image 115
Estus Flask Avatar answered Sep 18 '22 01:09

Estus Flask