6

Is it a good practice to inject 'service' dependencies in @Input properties? The service in this context is not a singleton instance managed at the root level but multiple instances of different implementations of the interface.

Consider the following example: In an Angular library, the ShapeComponent has a dependency on ShapeService (interface).

Component

@Component({
  selector: 'ex-shape',
  templateUrl: '..',
})    

export class ShapeComponent {
   constructor(shapeServiceCtor: ShapeService)
    
   @Input shapeServiceInput: ShapeService;
}

A simple way to resolve the dependency is to set the input property as shown in the following code.

<ex-shape [shapeServiceInput]="rectangleShapeService" />
<ex-shape [shapeServiceInput]="ellipseShapeService" />
<ex-shape [shapeServiceInput]="polygonShapeService" />

Does the above approach hold good in resolving dependencies in Components?

If the input property approach is used then the services/dependencies have to the propagated to the child components in the same fashion. The downside of this approach is that the parent component will have to accept all the dependencies as input properties.

Are there any recommended approaches to inject and scope dependencies at the library level?

KDR
  • 499
  • 4
  • 16

6 Answers6

5

You're not really using Angular's dependency injection at this point, which in your case I'm not sure is good or bad.

If the ShapeComponent has no way to know what instance of the service it's using, and any time you call it you need to pass an arbitrary instance, this should be ok.

If the parent of the ShapeComponent will always pass the same instance of the service, the parent could include it in its providers array and then children ShapeComponents will use that same instance.

Angular's docs have some more detailed info on the DI Hierarchy https://angular.io/guide/hierarchical-dependency-injection

Your call to constructor(shapeServiceCtor: ShapeService) will also lead to some confusion, as the component will have both the DI injected one, and another (or possibly the same) instance from the @Input

cjd82187
  • 3,101
  • 3
  • 13
  • 17
  • The constructor parameter was included to discuss the alternative approach, either the constructor or the input dependency will be followed finally. If the input property approach is used then the services have to the propagated to the child components in the same fashion. – KDR Jul 09 '20 at 08:13
1

This could work in your case:

@Component({
  selector: 'ex-shape',
  templateUrl: '..',
})    
export class ShapeComponent {
   // change the type to any since any service will be comping as input
   private shapeServiceCtor: any;

   constructor(
       private injector: Injector // injector to inject incoming services
   ) {}

   // type as any
   @Input shapeServiceInput: any;

   ngOnInit() {
       // this will inject the service
       this.shapeServiceCtor = this.injector(shapeServiceInput);
   }
}
hanch
  • 4,195
  • 2
  • 7
  • 32
0

"Injecting" a service like this will not work. You will have to pass an instance of said service. Instead, try to create multiple shape components and if they share functionality, then let them extend a base component OR use a service to be shared between them.

Akk3d1s
  • 201
  • 2
  • 7
0

You should set providedIn to be true for the @Injectable decorator.

Your idea is to use a singleton and singleton injections could be implemented by setting providedIn as true.

Then you don't need to use @Input decorator in depth and you can simply import the service and use it inside the components where you are gonna use.

So the format should be like this.

@Injectable({
    providedIn: true
})
export class SomeService {
 ...
}
Alan Zhou
  • 73
  • 1
  • 8
0

Remove the parameter from the @Injectable () decorator. It ensures that the service is not provided at the root level:

@Injectable()
export class ShapeService{

You can declare a service at a component level, and you can inject the service as normal into any child components. The child components will get the same instance as the parent.

In the ShapeComponent, you can provide the service in the @Component Decorator:

@Component({
  selector: 'ex-shape',
  templateUrl: '..',
  providers:  [ ShapeService ]
})    

export class ShapeComponent {
   constructor(private shapeServiceCtor: ShapeService)

This assumes that you want a new instance of the ShapeService for each ShapeComponent.

justintimeza
  • 306
  • 2
  • 4
0

@Input and @Output are for communication, communication between one component to another. @Input will expose our variables to the outside of the component. Dependency Injection is a different concept, In dependency injection, we inject components and build-in classes in the constructor and this will make available for us. @Input is definitely not a correct approach.