Angular 依赖注入:继承和服务

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

Angular Dependency Injection: Inheritance and Services

问题

以下是您要翻译的内容:

"Suppose I have the following setup:"

export const REPOSITORY_NAME = new InjectionToken<string>('RepositoryName');
export const BASE_ENTITY_NAME = new InjectionToken<string>('BaseEntityName');

@Injectable()
export class RepositoryBase<TBaseEntity> {
 
  constructor(
    @Self() @Inject(REPOSITORY_NAME) private _name: string,
    private _api: ApiBaseService<TBaseEntity>
  ) {}

@Injectable()
export class ApiBaseService<TBaseEntity> {

  constructor(
    private _http: HttpClient,
    @Inject(BASE_ENTITY_NAME) private _baseEntityName: string,
  ) { }

"I now want to create a repository which inherits from my RepositoryBase and uses the base service:"

@Directive({ // &lt;-------- [1]
  providers: [
    { provide: BASE_ENTITY_NAME, useValue: 'Car' },
    { provide: REPOSITORY_NAME, useValue: 'CarRepository'},
  ]
})
export class CarRepository extends RepositoryBase<Car> {
}

"This is not working, I suspect one of the main reasons being, that I cannot inject a token into the parent I am inheriting from. I thought @Self() could solve this, it doesn't."

"I got rid of the @Injectable() decorator in the base class and passed all arguments directly into the RepositoryBase via my CarRepository constructor. But this does not feel right and still doesn't get the injections towards the ApiBaseService working. Maybe, because the 'middle' class isn't injectible by itself."

"No matter what I try, I always end up with something like:"

ERROR NullInjectorError: R3InjectorError(MyFeatureModule)[CarRepository -> InjectionToken RepositoryName]: 
  NullInjectorError: No provider for InjectionToken RepositoryName!

"I have also tried providing the BaseApiService in root, didn't help either. The RepositoryBase is not provided, only the child CarRepository is provided in MyFeatureModule."

"In addition, as denoted by [1], why do I have to use @Directive() in order to use providers? Why is an @Injectable() not allowed to have providers?"

"Background:"

"I want to inject multiple repositories (CarRepository, BicycleRepository etc.) into the same component, that's why I can't do that at the component level (or at least I don't know a way of doing that)"

"Am I fundamentally wrong about how this works?"

"Edit:"

"To be clear, CarRepository is only a directive because a pure injectable cannot have providers as mentioned above. I would like to use this Repository like a normal service in a component:"

@Component(
  template: '...',
  selector: 'my-component'
})
export class MyComponent {

  constructor(
    private _carRepository: CarRepository
  ) { }
}

"Or is this not a good use-case for Angular DI? I could of course simply use standard constructor arguments and create everything myself, but that doesn't feel very angular-like."

英文:

Suppose I have the following setup:

export const REPOSITORY_NAME = new InjectionToken&lt;string&gt;(&#39;RepositoryName&#39;);
export const BASE_ENTITY_NAME = new InjectionToken&lt;string&gt;(&#39;BaseEntityName&#39;);


@Injectable()
export class RepositoryBase&lt;TBaseEntity&gt; {
 
  constructor(
    @Self() @Inject(REPOSITORY_NAME) private _name: string,
    private _api: ApiBaseService&lt;TBaseEntity&gt;
  ) {}


Injectable()
export class ApiBaseService&lt;TBaseEntity&gt; {

  constructor(
    private _http: HttpClient,
    @Inject(BASE_ENTITY_NAME) private _baseEntityName: string,
  ) { }

I now want to create a repository which inherits from my RepositoryBase and uses the base service:

@Directive({ // &lt;-------- [1]
  providers: [
    { provide: BASE_ENTITY_NAME, useValue: &#39;Car&#39; },
    { provide: REPOSITORY_NAME, useValue: &#39;CarRepository&#39;},
  ]
})
export class CarRepository extends RepositoryBase&lt;Car&gt; {
}

This is not working, I suspect one of the main reasons being, that I cannot inject a token into the parent I am inheriting from. I thought @Self() could solve this, it doesn't.

I got rid of the @Injectable() decorator in the base class and passed all arguments directly into the RepositoryBase via my CarRepository constructor. But this does not feel right and still doesn't get the injections towards the ApiBaseService working. Maybe, because the "middle" class isn't injectible by itself.

No matter what I try, I always end up with something like:

ERROR NullInjectorError: R3InjectorError(MyFeatureModule)[CarRepository -&gt; InjectionToken RepositoryName]: 
  NullInjectorError: No provider for InjectionToken RepositoryName!

I have also tried providing the BaseApiService in root, didn't help either.
The RepositoryBase is not provided, only the child CarRepository is provided in MyFeatureModule.

In addition, as denoted by [1], why do I have to use @Directive() in order to use providers? Why is an @Injectable() not allowed to have providers?

Background:

I want to inject multiple repositories (CarRepository, BicycleRepository etc.) into the same component, that's why I can't do that at the component level (or at least I don't know a way of doing that)

Am I fundamentally wrong about how this works?

Edit:
To be clear, CarRepository is only a directive because a pure injectable cannot have providers as mentioned above.
I would like to use this Repository like a normal service in a component:

@Component(
  template: &#39;...&#39;,
  selector: &#39;my-component&#39;
})
export class MyComponent {

  constructor(
    private _carRepository: CarRepository
  ) { }
}

Or is this not a good use-case for Angular DI? I could of course simply use standard constructor arguments and create everything myself, but that doesn't feel very angular-like.

答案1

得分: 1

指令具有提供者,因为它与DI树中的实际节点相关,它用于某种元素,例如 <div my-directive></div>。可注入的是可以注入的东西。在大多数情况下,它是一个服务。

如果我理解问题正确,您需要多个存储库,并且希望将它们用作单独的类型化类。我建议您类似于以下方式:

@Injectable()
export class RepositoryBase<TBaseEntity> {
  private _api = inject(ApiBaseService<TBaseEntity>);
  constructor(
    private _name: string,
  ) { if(!_name) throw new Error('无法构建没有名称的存储库');}
}

@Injectable()
export class ApiBaseService<TBaseEntity> {
  private _http = inject(HttpClient);
  constructor(
    private _baseEntityName: string,
  ) { }
}

@Injectable({provideIn: 'root'})
export class CarRepository extends RepositoryBase<Car> {
  constructor() { super('CarRepository'); }
}

@Injectable({provideIn: 'root'})
export class ApiCarService extends ApiBaseService<Car> {
  constructor() { super('Car'); }
}

如果您希望在某个组件子树中使某些逻辑通用(例如,在某个页面上,您只使用 CarRepository,但组件应该能够接受您的任何存储库),您可以通过DI来实现。例如,在Angular的最新版本中,通过路由配置可以实现:

// 路由
{
  path: 'car-things',
  loadChildren: () => import('./cars/car.module'),
  providers: [
    {provide: ApiBaseService, useExisting: ApiCarService},
    {provide: RepositoryBase, useExisting: CarRepository},
  ]
}

// 使用
@Component({...})
export FiltersGenericComopnent {
  private repository = inject(RepositoryBase) // 如果在 'car-things' 子树中渲染,将是 CarRepository
}

这是您提供的内容的翻译。

英文:

directive has providers because it is related to a real node in DI tree, as it is used on some kind of element. for example &lt;div my-directive&gt;&lt;/div&gt;. injectable is something that can be injected. in most cases it is a service.

If I got the problem right, you need to have several repositories and you want to use them as separate typed classes. I would propose you something like this:

@Injectable()
export class RepositoryBase&lt;TBaseEntity&gt; {
  private _api = inject(ApiBaseService&lt;TBaseEntity&gt;)
  constructor(
    private _name: string,
  ) { if(!_name) throw new Error(&#39;cannot construct repository without a name&#39;);}


@Injectable()
export class ApiBaseService&lt;TBaseEntity&gt; {
  private _http = inject(HttpClient);
  constructor(
   private _baseEntityName: string,
  ) { }


// ...
@Injectable({provideIn: &#39;root&#39;})
export class CarRepository extends RepositoryBase&lt;Car&gt; {
 constructor() {super(&#39;CarRepository&#39;); }
}

@Injectable({provideIn: &#39;root&#39;})
export class ApiCarService extends ApiBaseService&lt;Car&gt; {
  constructor(
) { super(&#39;Car&#39;); }

If you want to have some logic generic in some component subtree (for example on some page you only work with CarRepository, but components should be able to accept any of your repositories) you could reuse that with the help of DI. for example in latest version of angular through router configuration

// route
{
  path: &#39;car-things&#39;,
  loadChildren: () =&gt; import(&#39;./cars/car.module&#39;),
  providers: [
    {provide: ApiBaseService, useExisting: ApiCarService},
    {provide: RepositoryBase, useExisting: CarRepository},
  ]
}

// usage
@Component({...})
export FiltersGenericComopnent {
  private repository = inject(RepositoryBase) // will be CarRepository, if rendered in &#39;car-things&#39; subtree
}

huangapple
  • 本文由 发表于 2023年3月4日 06:26:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/75632359.html
匿名

发表评论

匿名网友

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

确定