Incorporating dynamic dependency injection in Angular 2 through the use of @Input()

Let's consider a scenario where an Angular 2 component-directive needs to dynamically determine the injected dependency it uses based on an @Input().

For example, I would like to be able to specify

<trendy-directive use="'serviceA'">
and have that particular instance of TrendyDirective utilize serviceA. Alternatively, I could specify `'serviceB'` to make it use serviceB instead. (Note: this is a simplified version of my actual objective)

If you believe this approach is flawed, I welcome your feedback along with an explanation.

Here is a basic implementation to illustrate my concept. Assume ServiceA and ServiceB are injectable classes that both implement iService with a method 'superCoolFunction'.

@Component({
    selector: 'trendy-directive',
    ...
})
export class TrendyDirective implements OnInit {
    constructor(
        private serviceA: ServiceA,
        private serviceB: ServiceB){}

    private service: iService;
    @Input() use: string;

    ngOnInit() {
        switch (this.use){
            case 'serviceA': this.service = this.serviceA; break;
            case 'serviceB': this.service = this.serviceB; break;
            default: throw "There's no such thing as a " + this.use + '!';
        }
        this.service.superCoolFunction();
    }
}

While this solution may technically function, there could be more efficient methods for achieving dynamic dependency injection.

Answer №1

Here is a snippet of code:

// This can be utilized as a service for overriding and testing purposes
export const specialServiceMap = {
  serviceX: ServiceX,
  serviceY: ServiceY
}

constructor(private injector: Injector) {}    
...
ngOnInit() {
    if (specialServiceMap.hasOwnProperty(this.use)) {
        this.service = this.injector.get<any>(specialServiceMap[this.use]);
    } else {
        throw new Error(`The service '${this.use}' does not exist`);
    }
}

Answer №2

The Angular2 documentation provides a similar approach when it comes to dependency injection, which can be found in the InjectorComponent section.

@Component({
    providers: [Car, Engine, Tires, heroServiceProvider, Logger]
})
export class InjectorComponent {
     car: Car = this.injector.get(Car);
     heroService: HeroService = this.injector.get(HeroService);
     hero: Hero = this.heroService.getHeroes()[0];

     constructor(private injector: Injector) { }
}

To implement this, you need to include the Injector in the constructor and specify all services in the providers property of the @Component annotation. Then, you can use injector.get(type), where type is resolved from your @Input. According to the documentation, the Service is not actually injected until requested (.get()).

Answer №3

Building upon the insights shared by Estus Flask, I propose a method that takes service importation to the next level by dynamically importing the service rather than explicitly declaring array objects.

Essentially, all we need to do is provide the path and name of the service, and the process remains largely unchanged.

public _dynamicService: any;

dynamicDI(service_path, service_name){
    import(service_path).then(s => {

      this._dynamicService = this.injector.get<any>(s['service_name']);

    })
}

With this implementation, you can now utilize functions within the dynamicService as demonstrated below:

(For instance, let's consider a scenario where we have an HTTP observable function in the required service)

this._dynamicService['your_function_name']().subscribe(res=> { console.log(res) } );

Answer №4

Discover an intricate yet effective method here!

Establish a default search service within a shared module along with multiple custom implementations, all without the need to reference each implementation explicitly.

Interface and default implementation

export interface ISearch {
    searchByTerm(textInput: string);
}

export class DefaultSearch implements ISearch {
    searchByTerm(textInput: string) { console.log("default search by term"); }
}

Create a list of service implementations using InjectionToken

 // Maintain a list of tokens where providers will supply implementations
 export const SearchServiceTokens: Map<string, InjectionToken<ISearch>> = new Map();
 // Add token for default service implementation
 SearchServiceTokens.set('default', new InjectionToken<ISearch>('default'));

Provider for default service implementation

   providers: [
      ...
      // Default service implementation
      {
         provide: SearchServiceTokens.get('default'),
         useClass: DefaultSearch
      }
   ]

Custom implementation (possible in another module)

export class Component1Search implements ISearch {
    searchByTerm(textInput: string) { console.log("component1 search by term"); }
}

Add token for custom implementation

SearchServiceTokens.set('component1', new InjectionToken<ISearch>('component1'));

Add provider

   providers: [
      ...
      // Other implementation service
      {
         provide: SearchServiceTokens.get('component1'),
         useClass: Component1Search
      }
   ]

Lastly, implement it in your component

    @Input() useService;
    searchService: ISearch;

    constructor(private injector: Injector) {
       // Use default if no preference provided
       let serviceToUse = 'default';
       if (null !== this.useService) { serviceToUse = this.useService; }
       this.searchService = this.injector.get(SearchServiceTokens.get(serviceToUse));
    }

Answer №5

There is a tool called Inject in the @angular/core module that allows for an alternative method of injection. However, it is limited to usage within constructors.

In order to utilize this tool, you must place the component's inputs in the inputs array of your @component decorator (without using the @Input decorator inside the class) and then inject the input variable in the constructor.

Answer №6

There may still be a chance to find an interesting and clever solution for dynamically injecting services based on certain conditions. One approach is to utilize a service wrapper and include a method or getter that retrieves the appropriate instance of the required service:

@Injectable({
    providedIn: 'root',
})
export class CredentialsServiceImp {      
    constructor(           
        private visitorService: VisitorCredentialsServiceImp,
        private employeeService: EmployeeCredentialsServiceImp
    ) {

    }
    instance(userType:UserType): UserCredentialsService {
        return userType == UserType.employee ? this.employeeService : this.visitorService;
    }

}

Both services adhere to an abstract class called UserCredentialsService, and the instance getter retrieves the correct instance based on a condition.

export class Component implements OnInit {
      @Input() userType : UserType;    
    constructor(           
        private credentials: CredentialsServiceImp  
    ) {
        
    }

     ngOnInit():  {
      const service = this.credentials.instance(this.userType);
    }  
   
   
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I lost my hovering tooltip due to truncating the string, how can I bring it back in Angular?

When using an angular ngx-datatable-column, I added a hovering tooltip on mouseover. However, I noticed that the strings were too long and needed to be truncated accordingly: <span>{{ (value.length>15)? (value | slice:0:15)+'..':(value) ...

Does Apollo Federation provide support for a code-first development approach?

I have declarations using 'code-first' approach in my project, but now I want to utilize them as microservices. How can I separate my 'typeDefs' and 'resolvers' following Apollo's 'schema-first' methodology? Is ...

The creation of fsm.WriteStream is invalid as it is not a recognized constructor

Can you help me with this issue? I am attempting to install @ng-idle/keepalive using the command npm install --save @ng-idle/core, but I encountered the following error: npm ERR! fsm.WriteStream is not a constructor npm ERR! Log files were not written due ...

What might be causing my observable to fail to return a value?

I'm currently utilizing an API known as ngx-pwa localstorage, which serves as a wrapper for an indexeddb database. Within my Angular project, I have a service that interacts with this database through a method called getItem: getItem(key: string) { ...

Divide the list of commitments into separate groups. Carry out all commitments within each group simultaneously, and proceed to the next group

My Web Crawling Process: I navigate the web by creating promises from a list of website links. These promises act as crawlers and are executed sequentially. For instance, if I have 10 links, I will crawl the first link, wait for it to complete, then move ...

I encountered an issue with Typescript Jest where it was unable to find the mock or mockReturnedValue functions on the types I

Let's test out this interesting class: //RequestHandler.js import axios, {AxiosInstance} from 'axios'; import settings from './settings'; const axiosHandler: AxiosInstance = axios.create({ baseURL: 'http://localhost:8081&a ...

How can we initiate an AJAX request in React when a button is clicked?

I'm fairly new to React and I'm experimenting with making an AJAX call triggered by a specific button click. This is how I am currently using the XMLHttpRequest method: getAssessment() { const data = this.data //some request data here co ...

Geolocation plugin in Ionic encountered an issue: "Geolocation provider not found"

I've been working on implementing geolocation in my ionic2 hello world project, and I successfully added the ionic plugin called "Geolocation" by following the instructions on the official website. After running these two commands: $ ionic plugin add ...

Creating components through the command line while managing multiple projects using Angular CLI 6

Currently, I am utilizing the most recent Angular CLI version (v6). Within my codebase resides a collection of applications housed within the projects directory. My objective is to create and organize various modules and components within these projects v ...

Customizing HttpErrorResponse object properties in Angular

I am new to working with Angular (8) and have been developing a frontend SPA that interacts with a Node Express backend API through http calls using the HttpClient module. In the API response, I can define an Http response status along with an additional J ...

What is preventing me from including an additional parameter in a function in TypeScript?

I am currently developing a task management application. I am facing an issue while attempting to incorporate the event and items.id into a button function for actions like delete, edit, or mark as completed. While this functionality works smoothly in pla ...

Decorators in Angular 4 using TypeScript are not permitted in this context

Here is some code that is throwing errors: let isBrowserFactory2=function(@Inject(PLATFORM_ID) platformId: string){ return isPlatformBrowser(platformId);} This results in the following error message: Decorators are not valid here And then we have this ...

Identify modifications to properties in Angular

What is the most effective method for responding to property changes within a component? I am seeking a solution that will trigger a specific function every time a property in a component is altered. Consider the following example with a single component: ...

Remove the Prisma self-referencing relationship (one-to-many)

I'm working with this particular prisma schema: model Directory { id String @id @default(cuid()) name String? parentDirectoryId String? userId String parentDirectory Directory? @relation("p ...

Discover the method of sending individual row data to a component using *ngFor in Angular 4

I need assistance with Angular as I am not very experienced in it. Here is the HTML code that I have: <tbody> <tr *ngFor="let data of employeeFilterLists"> <td>{{data.Code}}</td> <td (clic ...

There seems to be an issue with the subscription of a subject between two modules in Angular 5

Currently, I am in the process of developing a project using Angular 5. One requirement is to display an app loading spinner frequently. To achieve this, I have created a shared module along with a spinner component within it. Below is the content of my mo ...

Tips for preventing event propagation while clicking on a swiper.js image next/prev button within a bootstrap card

I have integrated ngx-swiper-wrapper within a bootstrap card as shown below, with a routerlink at the top. When I click anywhere on the card, it successfully routes to the designated page. However, I am facing an issue where I want to prevent routing if a ...

A TypeScript function that strictly checks for tuples without any union elements

Calling all TypeScript gurus! I am currently developing a versatile TypeScript function that can handle two different types of arguments: Class A and B. A and B are completely independent and not related through inheritance. The challenge I am facing is ...

When working in VScode, there may be difficulty in locating project-specific imports for `tsx` files. However, the TypeScript compiler is able to locate

When converting a jsx component to tsx, my VScode editor highlights project-specific imports as errors. The following code is from components\molecules\WizardSteps.jsx JSX https://i.stack.imgur.com/tKZ35.png TSX The following code is from comp ...

How to apply a single pipe to filter columns in Angular 2 with an array of values

I need to sort through an array of objects using multiple array string values. Here is an example of how my array of objects looks like: [{ "name": "FULLY MAINTAINED MARUTI SUZUKI SWIFT VDI 2008", "model": "Swift" }, { "name": "maruti suzuki ...