2

Based on edit3 here I implemented that solution. But do that Subject initialisation and "clean up" in ngOnDestroy in every component is still pain. I thought that it could be passed to decorator, eg. based on this post:

export function AutoUnsubscribe( constructor ) {

  const original = constructor.prototype.ngOnDestroy;
  constructor.prototype.ngUnsubscribe = new Subject<void>();

  constructor.prototype.ngOnDestroy = function () {
    constructor.prototype.ngUnsubscribe.next();
    constructor.prototype.ngUnsubscribe.complete();
    original && typeof original === "function" && original.apply(this, arguments);
  };

}

Usage is just like any other class decorator. And it works well, but I had to hack type system when using it, like this: takeUntil((this as any).ngUnsubscribe).

The property ngUnsubscribe is unrecognised by the type system in a component. Is there any way to tell the type system that this property is there or will be?

If this would be possible then it would be super easy to use this @AutoUnsubscribe decorator: just decorate a class and put takeUntil(this.ngUnsubscribe) before every subscribe. Wonder if we can do even better to just decorate a class and automagically this decorator would put takeUntil(this.ngUnsubscribe) before every subscribe for us.

My current workaround is to use BaseComponent with ngUnsubscribe prop. and extend every component which calls subscribe.

Community
  • 1
  • 1
Petr Marek
  • 1,381
  • 1
  • 15
  • 31
  • found this out the hard way: if ngOnDestroy is not present on the component at compile time, ng buld --prod --aot will optimize away any calls made to ngOnDestroy, even if the method is available at runtime. Solution: always have an ngOnDestroy when using @AutoUnsubscribe, even if it's empty – Dan Manastireanu Jun 20 '17 at 08:07
  • @DanManastireanu: Don't know if the `ngOnDestroy` is really required. At least in my environment with Angular 15 it works without. Just for the sake of completeness. – LeO Feb 28 '23 at 10:52

1 Answers1

0

Maybe I'm going to use too model example but I think there two ways to do this:

  • Create an interface that just tells the compiler about the class property

  • Extend the module that matches your file name and adds the property to the interface

1. Create an interface

export interface MyOtherClass {
  magicProperty: string;
}

export class MyOtherClass
{
  func(): void {
    console.log(this.magicProperty);
  }
}

2. Extend the module

// 43479078.ts
export class MyOtherClass
{
  func(): void {
    console.log(this.magicProperty);
  }
}

...and

// MyOtherClass-extension.d.ts
import { MyOtherClass } from './43479078';

declare module './43479078' {
  interface MyOtherClass {
    magicProperty: void;
  }
}

Just be aware that now when compiling you'll need to include both files or add /// <reference path="./MyOtherClass-extension.d.ts" /> at the top of 43479078.ts.

Also, I think the extended class needs to be exported because otherwise the file wouldn't be considered a module.

martin
  • 93,354
  • 25
  • 191
  • 226