Appropriate decorator for angular base service with lifecycle hooks?

huangapple go评论65阅读模式
英文:

Appropriate decorator for angular base service with lifecycle hooks?

问题

I have translated the code-related part of your text:

我有一个服务基类实现了 `OnDestroy` 来清理内部的 Subject

export abstract class BaseService implements OnDestroy {
  private subject = new Subject<void>();
  constructor( ... ) { }

  ngOnDestroy() {
    this.subject.complete();
  }
}
这个基类旨在通过子类调用 `super(...)` 来显式构建并且具有无法通过依赖注入提供的参数

子类旨在作为 Angular 单例服务

@Injectable({
  providedIn: 'root'
})
export class ChildService extends BaseService {
  constructor() {
    /* 手动构建并提供基类的构造函数参数 */
    super(...);
  }
}

请注意,这是代码的翻译部分。如果您需要更多帮助或有其他问题,请随时提出。

英文:

I've got a service base class that implements OnDestroy to clean up an internal Subject:

export abstract class BaseService implements OnDestroy {
  private subject = new Subject&lt;void&gt;();
  constructor( ... ) { }

  ngOnDestroy() {
    this.subject.complete();
  }
}

This base class is intended explicit construction via child class calling super(...), and has parameters that cannot be provided by dependency injection.

The child classes are intended to singleton angular services:

@Injectable({
  providedIn: &#39;root&#39;
})
export class ChildService extends BaseService {
  constructor() {
    /* manually build and provide constructor parameters to base */
    super(...);
  }
}

However, as of Angular version 10, even base or abstract classes that implement lifecycle hooks must be properly decorated, as denoted by the following error message:

Class is using Angular features but is not decorated. Please add an explicit Angular decorator.

I don't mind adding a decorator to get it recognized properly by the Angular infrastructure. However, of the four class decorators I know, none seem appropriate to use for the base class.

@Component and @NgModule are explicitly incorrect. @Injectable is close, but then it tries and fails to evaluate the base class's constructor for dependency injection, giving other errors (requesting the constructor parameters to be marked with the appropriate @Inject decorators).

@Directive seems to work without complaint, but this seems like a misuse of the decorator, and I wonder what kind of overhead is being added.

Is there a recommended solution for this pattern?

答案1

得分: 1

For your scenario, the recommended decorator to use is @Injectable({ providedIn: 'root' }) on your abstract base class BaseService. You can then use this decorator on your child services as well.

The issue you encountered with this decorator trying to evaluate the base class's constructor for dependency injection can be resolved by marking the constructor parameters in the base class with the @Optional() decorator. This indicates that the parameters are optional and should not be injected, allowing you to manually provide them in the child service constructor.

Here's an example of how your BaseService class should look with the @Injectable decorator and optional constructor parameters:

@Injectable({
  providedIn: 'root'
})
export abstract class BaseService implements OnDestroy {
  private subject = new Subject<void>();
  
  constructor(@Optional() private dependency1: Dependency1, @Optional() private dependency2: Dependency2) {
  }

  ngOnDestroy() {
    this.subject.complete();
  }
}

And your child service would look like:

@Injectable({
  providedIn: 'root'
})
export class ChildService extends BaseService {
  constructor(private dependency1: Dependency1, private dependency2: Dependency2) {
    super(dependency1, dependency2);
  }
}

With this setup, your base service can be properly decorated as an injectable, and you can manually provide constructor parameters for both the base and child services.

UPDATE

If marking the base class constructor parameters with the @Optional() decorator is still resulting in a compilation error, it's possible that the issue is related to the fact that you're using lambdas for the parameters.

In that case, you can try using the InjectionToken class to provide the dependencies in the child class constructor. Here's an example of how you can do this:

import { InjectionToken } from '@angular/core';

export abstract class BaseService implements OnDestroy {
  private subject = new Subject<void>();
  constructor(private dep1: any, private dep2: any) { }

  ngOnDestroy() {
    this.subject.complete();
  }
}

export const DEP1 = new InjectionToken<any>('dep1');
export const DEP2 = new InjectionToken<any>('dep2');

@Injectable({
  providedIn: 'root'
})
export class ChildService extends BaseService {
  constructor(@Inject(DEP1) dep1: any, @Inject(DEP2) dep2: any) {
    super(dep1, dep2);
  }
}

In the above code, we've defined two InjectionToken objects, one for each dependency. Then in the child class constructor, we use the @Inject() decorator to inject the dependencies using the tokens.

To provide the dependencies, you can use the providers array in the @NgModule decorator of your module:

@NgModule({
  providers: [
    { provide: DEP1, useValue: /* provide the dependency here */ },
    { provide: DEP2, useValue: /* provide the dependency here */ }
  ]
})
export class AppModule { }

With this approach, you should be able to provide the dependencies to the base class constructor, even if they're lambdas, without running into any compilation errors.

英文:

For your scenario, the recommended decorator to use is @Injectable({ providedIn: &#39;root&#39; }) on your abstract base class BaseService.
You can then use this decorator on your child services as well.

The issue you encountered with this decorator trying to evaluate the base class's constructor for dependency injection can be resolved by marking the constructor parameters in the base class with the @Optional() decorator. This indicates that the parameters are optional and should not be injected, allowing you to manually provide them in the child service constructor.

Here's an example of how your BaseService class should look with the @Injectable decorator and optional constructor parameters:

@Injectable({
  providedIn: &#39;root&#39;
})
export abstract class BaseService implements OnDestroy {
  private subject = new Subject&lt;void&gt;();
  
  constructor(@Optional() private dependency1: Dependency1, @Optional() private dependency2: Dependency2) {
  }

  ngOnDestroy() {
    this.subject.complete();
  }
}

And your child service would look like:

@Injectable({
  providedIn: &#39;root&#39;
})
export class ChildService extends BaseService {
  constructor(private dependency1: Dependency1, private dependency2: Dependency2) {
    super(dependency1, dependency2);
  }
}

With this setup, your base service can be properly decorated as an injectable, and you can manually provide constructor parameters for both the base and child services.

UPDATE

If marking the base class constructor parameters with the @Optional() decorator is still resulting in a compilation error, it's possible that the issue is related to the fact that you're using lambdas for the parameters.

In that case, you can try using the InjectionToken class to provide the dependencies in the child class constructor. Here's an example of how you can do this:

import { InjectionToken } from &#39;@angular/core&#39;;

export abstract class BaseService implements OnDestroy {
  private subject = new Subject&lt;void&gt;();
  constructor(private dep1: any, private dep2: any) { }

  ngOnDestroy() {
    this.subject.complete();
  }
}

export const DEP1 = new InjectionToken&lt;any&gt;(&#39;dep1&#39;);
export const DEP2 = new InjectionToken&lt;any&gt;(&#39;dep2&#39;);

@Injectable({
  providedIn: &#39;root&#39;
})
export class ChildService extends BaseService {
  constructor(@Inject(DEP1) dep1: any, @Inject(DEP2) dep2: any) {
    super(dep1, dep2);
  }
}

In the above code, we've defined two InjectionToken objects, one for each dependency. Then in the child class constructor, we use the @Inject() decorator to inject the dependencies using the tokens.

To provide the dependencies, you can use the providers array in the @NgModule decorator of your module:

@NgModule({
  providers: [
    { provide: DEP1, useValue: /* provide the dependency here */ },
    { provide: DEP2, useValue: /* provide the dependency here */ }
  ]
})
export class AppModule { }

With this approach, you should be able to provide the dependencies to the base class constructor, even if they're lambdas, without running into any compilation errors.

huangapple
  • 本文由 发表于 2023年4月17日 09:22:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76031137.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定