Using TypeScript to implement multiple instances of an abstract class for Angular's dependency injection

I have created an abstract class with its implementation.

export abstract class IPrint {
    abstract Print(textInput: string): void;
}

export class FilePrint implements IPrint {
    Print(textInput: string): void {
        console.log("File Print");
    }
}

Next, I will implement Angular DI:

  providers:
    [
      { provide: IPrint, useClass: FilePrint }
    ],

I can now utilize it in the following manner:

  constructor(private _print: IPrint) { }

  ngOnInit(): void {
    console.log(this._print.Print("HI"))
  }

Now, I aim to create multiple implementations of IPrint.

export class ScreenPrint implements IPrint {
    Print(textInput: string): void {
        console.log("Screen Print")
    }
}

Then add them to Angular DI:

  providers:
    [
      { provide: IPrint, useClass: FilePrint },
      { provide: IPrint, useClass: ScreenPrint }
    ],

However, when using IPrint, Angular encounters confusion on which implementation to use:

constructor(private _print: IPrint) { }

Answer №1

I encountered a similar issue and managed to resolve it by implementing an interface and creating a list of InjectionTokens. While it may seem like a bit much, this approach provides a great deal of flexibility and can be applied to various other challenges.

For one project, we established a global print service within a shared module, with multiple custom implementations being supplied by individual components. The beauty of this setup is that it eliminates the need to explicitly inject or reference all possible implementations in the global service.

Interface and initial implementation

export interface IPrint {
    Print(textInput: string): void;
}

export class FilePrint implements IPrint {
    Print(textInput: string): void {
        console.log("File Print");
    }
}

Creating a list of service implementations using InjectionToken

// Maintaining a list of tokens where providers will supply implementations
export const PrintServiceTokens: Map<string, InjectionToken<IPrint>> = new Map();
// Adding token for File service implementation
PrintServiceTokens.set('file', new InjectionToken<IPrint>('file'));

Provider for File service implementation

   providers: [
      ...
      // Providing the first implementation service
      {
         provide: PrintServiceTokens.get('file'),
         useClass: FilePrint
      }
   ]

Adding another implementation (which could be in a separate module)

export class ScreenPrint implements IPrint {
    Print(textInput: string): void {
        console.log("ScreenPrint");
    }
}

Adding a token for the additional implementation

PrintServiceTokens.set('screen', new InjectionToken<IPrint>('screen'));

Setting up the provider

   providers: [
      // Other implementation service
      {
         provide: PrintServiceTokens.get('screen'),
         useClass: ScreenPrint
      }
   ]

Finally, in a component or service

    myPrintService: IPrint;

    constructor(private injector: Injector) {
       // You can choose which service to use; a simple string could be provided via @Input
       this.myPrintService = this.injector.get(PrintServiceTokens.get('file'));
       this.myPrintService = this.injector.get(PrintServiceTokens.get('screen'));
    }

Answer №2

There can be confusion in Angular regarding which implementation to use:

However, Angular is designed to know exactly which implementation to use - in this case, it will utilize ScreenPrint. By overriding the injection token for IPrint, you are specifying the specific implementation to be used. It is important to note that you cannot have multiple implementations at the same time for a single injection token.

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

How to access type properties in typescript without using the "this" keyword

Below is a snippet of code that I am working with: class Player implements OthelloPlayer { depth; constructor(depth: number) { this.depth = depth; } getMove(state: OthelloState) { return this.MinimaxDecision(stat ...

Converting JavaScript code for Angular: A step-by-step guide

I have been working on integrating a feature similar to the one demonstrated in this Angular project: https://codepen.io/vincentorback/pen/NGXjda While the code compiles without any issues in VS code, I encountered two errors when attempting to preview it ...

The HTMLInputElement type does not contain a property named 'name'

function handleChange(e) { console.log(e.target.name); } <input name="bb" onChange={handleChange} /> Have you ever wondered why the HTMLInputElement element does not have a name attribute in React? ...

What are the best methods for identifying and handling transient upload errors in Azure blob storage?

I have a functional code for uploading files to Azure blob storage from frontend TypeScript. I need to handle errors that may occur during the upload process, such as network issues. How can we effectively catch and manage these errors on the client side ...

When subscribing to an Observable of type number or string (union type), NaN values are returned for string inputs

Within a component, I have a public member that is defined as follows: public docId$: Observable<number | string>; This means that the document ID can either be an integer or a string. Upon initializing in ngOnInit, I have the following code snippe ...

Employing a provider within a different provider and reciprocally intertwining their functions

I'm currently facing an issue with two providers, which I have injected through the constructor. Here's the code for my user-data.ts file: @Injectable() export class UserDataProvider { constructor(private apiService: ApiServiceProvider) { ...

Typescript - Postpone defining generic type until invoking function

Trying to modify an existing API by defining a generic type to determine the key/value pairs that can be passed to the function, and also for IntelliSense purposes. (Working with vue.js in this case, but it's not crucial.) Here is the structure of th ...

When transmitting data from NodeJS, BackBlaze images may become corrupted

I have been facing a challenge in my React Native app where I am attempting to capture an image and then post it to my NodeJS server. From there, I aim to upload the image to BackBlaze after converting it into a Buffer. However, every time I successfully u ...

Issue with launching Angular 6 project

I attempted the solution from this link, but unfortunately, it did not work for me. I have cloned a project from GitLab and am attempting to run it. First, take a look at the output of my ng version command: https://i.sstatic.net/qxRzk.png. The project i ...

Is TypeScript necessary, or can I simply stick with ES6?

As a client developer using AngularJS in my daily job, we are considering transitioning to TypeScript. After researching TypeScript, I discovered that most JavaScript packages I require need definition type files. This can be inconvenient, especially whe ...

Angular: handling asynchronous errors when no promise is utilized within a subscription

I am currently working with a material design table and have created custom functions to load the data and extract objects from a JSON array object. Here is a snippet of the code I am using: public getDocumentList() { return this.http.get(this.getDocu ...

How to achieve two-way binding using @Prop() in Vue Cli 3 with TypeScript?

Here is the source code snippet: This is the Child Component: <template> <v-snackbar v-model="showSnackbar" :bottom="y === 'bottom'" :left="x === 'left'" :multi-line="mode === 'multi-line'" :ri ...

"Series of Subscriptions: Unleashing New

I have a ReplaySubject that is subscribed to by multiple components. I am curious about the order in which these subscriptions are processed. public getValue$ = new ReplaySubject<any>(1); If I send a value to getValue$ using getValue$.next(5); Assu ...

Connect the HTML link component tag to a component that is passed through the constructor

I have recently started learning about Angular and TypeScript. Within my AppComponent HTML file, I include another component using the code <app-listpost></app-listpost> In the TypeScript file, I import the ListPostComponent into my AppCompon ...

Using Angular 2: Exploring the power of observables for broadcasting events during a forEach loop

Upon testing the service within a forEach loop, I noticed that the parameter I passed to the service ended up being the last one in the iteration. I initially suspected that the issue was due to closures, so I attempted using an anonymous function to add ...

Waiting for the HTTP Post response in Angular 2

My Angular2 app is written in TypeScript. I encounter an issue when making a HTTP Post request to create a new record, as the return value from the service does not come back in time to update the DOM with the newly created record. Is there a way to pause ...

TypeScript encounters challenges with implementing Redux containers

I'm struggling to understand the correct way to type Redux containers. Let's consider a simple presentational component that looks like this: interface MyProps { name: string; selected: boolean; onSelect: (name: string) => void; } clas ...

Using Angular to create a dynamic form with looping inputs that reactively responds to user

I need to implement reactive form validation for a form that has dynamic inputs created through looping data: This is what my form builder setup would be like : constructor(private formBuilder: FormBuilder) { this.userForm = this.formBuilder.group({ ...

Trouble retrieving child structural directive within parent structural directive

Seeking to connect parent structural directive with child structural directive. This shows my attempt to access the child element @Directive({ selector: '[appChildStruralDirective]' }) export class ChildStruralDirective { constructor(privat ...

Troubleshooting Issue with Angular 5: Inability to Hide Elements for Non-Authenticated Users

Below is the code from app.component.html <nav class='navbar navbar-default'> <div class='container-fluid'> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-targ ...