dependency injection - Can I define a provider scope without listing all providers in that scope in Angular? - Stack Overflow

I have some Angular services that store state, and I’d like these to be scoped to the appropriate compo

I have some Angular services that store state, and I’d like these to be scoped to the appropriate component (and its subcomponents) to avoid leaking state between different instances of the same component. As there can be many services that exist in the same scope, and some are only referenced in lazy components, I’d like to specify the scope in the service rather than the scoping component.

The current two options I’m aware of are as follows:

  1. Root provider

    @Injectable({ providedIn: 'root' }) class FirstService {}
    
    @Injectable({ providedIn: 'root' }) class SecondService {}
    
    @Component({
      template: `@defer { <lazy-component /> }`
      imports: [LazyComponent],
    }) class MyComponent {
      // same service instance for all MyComponent instances
      field = inject(FirstService)
    }
    
    @Component({}) class LazyComponent {
      // same service instance for all LazyComponent instances
      service = inject(SecondService);
    }
    

    This causes leaks between component instances, and is not tree-shakable for services not loaded in the first bundle.

  2. Explicit component provider

    @Injectable() class FirstService {}
    
    @Injectable() class SecondService {}
    
    @Component({
      template: `@defer { <lazy-component /> }`
      imports: [LazyComponent],
      providers: [FirstService, SecondService],
    }) class MyComponent {
      // new service instance for each MyComponent instance
      field = inject(FirstService)
    }
    
    @Component({}) class LazyComponent {
      // new service instance for each MyComponent instance
      service = inject(SecondService);
    }
    

    This is correctly separated, but it requires listing every service, and all services are loaded, even if they are not needed, or first needed in a lazy-loaded module.

Ideal solution

@Injectable({ scope: 'modal' }) class FirstService {}

@Injectable({ scope: 'modal' }) class SecondService {}

@Component({
  template: `@defer { <lazy-component /> }`
  providerScopes: ['modal'],
  imports: [LazyComponent],
}) class MyComponent {
  // new service instance for each MyComponent instance
  service = inject(FirstService)
}

@Component({}) class LazyComponent {
  // new service instance for each MyComponent instance
  service = inject(SecondService);
}

This way the services are scoped correctly, but also not listed in the service, and so can be lazy-loaded by child components.

Is there a way to achieve this?

I have some Angular services that store state, and I’d like these to be scoped to the appropriate component (and its subcomponents) to avoid leaking state between different instances of the same component. As there can be many services that exist in the same scope, and some are only referenced in lazy components, I’d like to specify the scope in the service rather than the scoping component.

The current two options I’m aware of are as follows:

  1. Root provider

    @Injectable({ providedIn: 'root' }) class FirstService {}
    
    @Injectable({ providedIn: 'root' }) class SecondService {}
    
    @Component({
      template: `@defer { <lazy-component /> }`
      imports: [LazyComponent],
    }) class MyComponent {
      // same service instance for all MyComponent instances
      field = inject(FirstService)
    }
    
    @Component({}) class LazyComponent {
      // same service instance for all LazyComponent instances
      service = inject(SecondService);
    }
    

    This causes leaks between component instances, and is not tree-shakable for services not loaded in the first bundle.

  2. Explicit component provider

    @Injectable() class FirstService {}
    
    @Injectable() class SecondService {}
    
    @Component({
      template: `@defer { <lazy-component /> }`
      imports: [LazyComponent],
      providers: [FirstService, SecondService],
    }) class MyComponent {
      // new service instance for each MyComponent instance
      field = inject(FirstService)
    }
    
    @Component({}) class LazyComponent {
      // new service instance for each MyComponent instance
      service = inject(SecondService);
    }
    

    This is correctly separated, but it requires listing every service, and all services are loaded, even if they are not needed, or first needed in a lazy-loaded module.

Ideal solution

@Injectable({ scope: 'modal' }) class FirstService {}

@Injectable({ scope: 'modal' }) class SecondService {}

@Component({
  template: `@defer { <lazy-component /> }`
  providerScopes: ['modal'],
  imports: [LazyComponent],
}) class MyComponent {
  // new service instance for each MyComponent instance
  service = inject(FirstService)
}

@Component({}) class LazyComponent {
  // new service instance for each MyComponent instance
  service = inject(SecondService);
}

This way the services are scoped correctly, but also not listed in the service, and so can be lazy-loaded by child components.

Is there a way to achieve this?

Share Improve this question edited Nov 20, 2024 at 17:22 Charlie Harding asked Nov 20, 2024 at 16:04 Charlie HardingCharlie Harding 6858 silver badges24 bronze badges 4
  • 1 The way I would do this is with route providers. – Matthieu Riegler Commented Nov 20, 2024 at 16:19
  • @MatthieuRiegler I’m not using Angular router, because I’m using Angular Elements and don’t have multiple pages. – Charlie Harding Commented Nov 20, 2024 at 16:21
  • Then you could go with component provided services ? It's a bit more verbose I agree. – Matthieu Riegler Commented Nov 20, 2024 at 16:29
  • @MatthieuRiegler That’s the second option I’ve given, but apart from being verbose, it also means that all classes scoped for a given component have to be loaded by that given component, even if they’re only used in the lazy child of that component. – Charlie Harding Commented Nov 20, 2024 at 16:34
Add a comment  | 

1 Answer 1

Reset to default 0

The providedIn property accepts the following inputs.

@Injectable ({
  providedIn?: Type<any> | "root" | "platform" | "any" | null | undefined;
})

In the providedIn property you can also specify Type<any>, which will be the component you want to scope the service to. But you can only scope it to a single component using this method.

import { Injectable } from '@angular/core';
import { LazyComponent, MyComponent } from './main';

@Injectable({
  providedIn: MyComponent,
})
export class FirstService {
  test = '';
}

@Injectable({
  providedIn: LazyComponent,
})
export class SecondService {
  test = '';
}

But this does not mean it is included in the application, to ensure it is findable by dependency injection, we can add it to the root providers array or root module providers array. But the scope defined in providedIn scopes it to that particular component.

bootstrapApplication(App, {
  providers: [FirstService, SecondService],
});

We can still import this service on other components, but it's guaranteed that the component you scoped it to has a separate instance.

Full Code:

services:

import { Component, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { FirstService, SecondService } from './services';

@Component({
  selector: 'lazy-component',
  standalone: true,
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="service.test"/>
    {{service.test}}
  `,
})
export class LazyComponent {
  service = inject(SecondService);
}

@Component({
  selector: 'my-component',
  standalone: true,
  imports: [LazyComponent],
  template: `@defer { <lazy-component /> }`,
})
export class MyComponent {
  service = inject(FirstService);
}
@Component({
  selector: 'app-root',
  imports: [FormsModule, MyComponent],
  standalone: true,
  template: `
    <my-component/> 

    <input [(ngModel)]="service.test"/>
    {{service.test}}
  `,
})
export class App {
  service = inject(FirstService);
  name = 'Angular';
}

bootstrapApplication(App, {
  providers: [FirstService, SecondService],
});

Stackblitz Demo

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742346456a4426641.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信