3

I'm trying to inject a list of instances to my Angular component but since it doesn't work and I can't find any documentation on it I think it's maybe not possible. So in this case my question would be how to achieve the same effect in a clean way without handwiring anything.

In Spring for example I would have a few concrete actions that extend from an abstract Action class or implement the Action interface.

@Component
class MyAction1 extends Action {
   ...
}

@Component
class MyAction2 extends Action {
   ...
}

@Component
class MyConsumer {
    @Autowired
    List<Action> actions;
}

Since the MyAction1 and MyAction2 are loaded in the runtime, MyConsumer will have its list filled with 2 instances. Is there any way to do this in Angular without handwiring?

EDIT: In Angular code what I want would be:

export abstract class ActionBase {
...
}

@Injectable()
export class MyAction1 extends ActionBase {
...
}

@Injectable()
export class MyAction2 extends ActionBase {
...
}

@Component(...)
export class MyConsumerComponent {
    constructor(
        private availableActions: ActionBase[]
    ) { }
}

I would expect MyConsumerComponent to have the two actions inside availableActions when it is initialized.

Cheers

0

3 Answers 3

5

This guy explain how do make the multiple injection of same contract using a more elegant way. The attribute multi in you provider make the magic!

https://blog.thoughtram.io/angular2/2015/11/23/multi-providers-in-angular-2.html#other-multi-providers

Only need change OpaqueToken for new class InjectionToken and your description.

An example:

export const MY_TOKEN = new InjectionToken<Contract[]>('my-token');

@NgModule({
  providers: [
    { provide: MY_TOKEN, useClass: OneContract, multi: true},
    { provide: MY_TOKEN, useClass: TowContract, multi: true},
    { provide: MY_TOKEN, useClass: ThreeContract, multi: true},
  ]
})
export class MyModule { }

And in your Component or Service:

@Injectable({ providedIn: 'root'})
export class MyService
{
  constructor(@Inject(MY_TOKEN) private readonly contracts: Contract[]) {}
}
Sign up to request clarification or add additional context in comments.

1 Comment

Kudos, elegant solution
2

Since the MyAction1 and MyAction2 are loaded in the runtime, MyConsumer will have its list filled with 2 instances. Is there any way to do this in Angular without handwiring?

No there isn't anything in Angular that does this.

Injectables in Angular are created on demand. So unless something explicitly injects the provider it won't be created. So there is no run-time way of MyAction2 adding itself to an array somewhere because something has to first consume it. For example; you might be tempted to add it to an array in the constructor, but if an instance is never created it won't be added.

There is always the option of creating your own TypeScript decorators that access the global injector and create a custom provider. I've seen some libraries do this such as NGXS.

I would just keep it simple and do it manually.

export const PROVIDERS:Action[] = [MyAction1, MyAction2];

export const PROVIDERS_TOKEN: InjectionToken<Action[]> = new InjectionToken<Action[]>('PROVIDERS_TOKEN');

@NgModule({
     providers: [
        {provide: PROVIDERS_TOKEN: useValue: PROVIDERS}
});

You can then inject it into another component via the constructor or property.

@Component()
public MyComponent {
     public constructor(@Inject(PROVIDERS_TOKEN) providers: Action[]) {
     }
}

Or via the property

@Component()
public MyComponent {
     @Inject(PROVIDERS_TOKEN)
     public providers: Action[];
}

1 Comment

Thanks, too bad it doesn't work like this. This is one of the cases where I love Spring DI :). But I like your approach with the constants, this cleans it up quite a lot.
-1

Do it through the constructor :

export class MyClass {
  constructor(
    private service: MyService
  ) {}
}

Using an access modifier in the constructor parameters create an instance member, meaning you don't have to manually wire it to a class member like you would do in Java.

In Angular, classes decorated with @Injectable() are singletons that can be injected into components like this. Services are one of them.

EDIT I don't think you can inject several classes at once, and you can't check if a class implements an interface (well, you can, but that's not interface-related).

What you can do, is use the ApplicationRef to get all of the instances of all the components currently in your application, check if they have what you want, and operate on them.

4 Comments

Hey, thanks for the answer. I know I can inject a class through the constructor, my question is, how I can inject a list of classes sharing a common interface in a component, I will probably add some Angular code to make this more clear.
I didn't understood that sorry. But I edited my question to answer what you just told me.
Hm that's a shame,... Interfaces don't work since they are compile time only in typescript. I thought about the solution with ApplicationRef but it seemed a bit hacky to me. I will see if I can dig up something.
Good luck with that ! (for information if you don't know, you can use Class.constructor.name, keyof, instanceof, and a lot of operators to check if the variable implements or not an interface)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.