Injecting services with an abstract class is a common practice in Angular library modules

In my development workflow, I have established an Angular Component Library that I deploy using NPM (via Nexus) to various similar projects. This library includes a PageComponent, which contains a FooterComponent and a NavbarComponent. Within the NavbarComponent, there is a button that triggers a function called logout. To enable this functionality, the function must be provided through a PageService specific to each project. For this purpose, I introduced an AbstractPageService within the Angular Component Library (PageService extends AbstractPageService).

Initially, I addressed this requirement by using an EventEmitter. However, as it necessitated providing a separate logout function for every new page, I sought a more efficient solution employing a single service per project. I achieved this by passing the respective PageService (for each project) using the forRoot() method of the Angular Component Library.

Although everything functions as intended, I am curious to explore whether there might be a more optimal approach or if this current solution suffices?

Below is the implementation of this solution:

Components Lib - components.module.ts

// Inserted code for ComponentsModule 

Component Lib - page.component.ts

// Inserted code for PageComponent 

Component Lib - abstract-page.service.ts

// Inserted code for AbstractPageService 

Utilization within a project is demonstrated below:

Project - app.module.ts

// Inserted code for AppModule 

Project - page.service.ts

// Inserted code for PageService 

Answer №1

After implementing this strategy, I encountered a common issue:

I noticed that the PageService was not behaving as expected in my application. Due to the fact that it is utilized in multiple modules (both in the application module and library module), each module generates a new instance of the service.

To work around this problem, I decided to provide my service using a string within the application module:

// app.module.ts located in the root directory of the application
providers: [
  {
    provide: 'PageService',
    useClass: PageService
  }
]

Then, whenever necessary, I injected the service by utilizing @Inject()

// anywhere within the root application where the service is required
constructor(@Inject('PageService') private pageService: PageService) {}

Subsequently, within the library, I established a basic interface:

export interface AbstractPageService {
  logout(): void;
}

This allowed me to easily inject the service in the library by employing the Inject() decorator and specifying its type using the interface (without having to implement the interface in the root application):

// anywhere in the library where the service is used
constructor(@Inject('PageService') private pageService: AbstractPageService) {}

As a result, both the main application and the library now share the same singleton instance of the PageService, thanks to the provision made in the root application.

Answer №2

To utilize a singleton pattern

Include in the main appModule.ts file

{ provide: 'currentCompanyService', useFactory: getCompanyServiceInstance, deps: [AppInitService]}

Create a factory function that simply returns the injected instance.

export function getCompanyServiceInstance(appInitService: AppInitService): AppInitService {
 return appInitService;
}

Use in a component of the library

public CustomLibraryComponent {

   private companyService: AppInitService;

   constructor(@Inject('currentEnvironment') environment: any,
               @Inject('currentCompanyService') companyService: AppInitService) {
       this.environment = environment;
       this.companyService = companyService;
   }
}

Answer №3

According to Sunil Singh, the recommended approach is to use a standard abstract class as the solution, without the need for @Injectable.

export abstract class AbstractPageService {

  abstract logout(): void;

}

Answer №4

It appears that 'useValue' should be used instead of 'useClass'.

export class ComponentsModule {
  static forRoot(pageService): ModuleWithProviders {
    return {
      ngModule: ComponentsModule,
      providers: [
        {provide: 'PageService', useValue: pageService}
      ]
    };
  }
}

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

Retrieve a list of class names associated with a Playwright element

Can anyone suggest the best method to retrieve an array of all class names for an element in Playwright using TypeScript? I've searched for an API but couldn't find one, so I ended up creating the following solution: export const getClassNames = ...

retrieve asynchronous data from the server using ngrx

How can I retrieve asynchronous data from the server? I am looking to save this data in a global store for future updates. I'm having trouble grasping the concept of asynchronous calls, such as in Redux. While I was able to understand it with simpl ...

Revamping every Angular component

Looking for a shortcut to upgrade all my Angular components at once. When checking my Angular version with ng v globally in VS Code terminal, results differ between overall and project directory settings: https://i.stack.imgur.com/2iOJx.png https://i.st ...

dynamic padding style based on number of elements in array

Is there a way to set a padding-top of 10px only if the length of model.leaseTransactionDto.wagLeaseLandlordDto is greater than 1? Can someone provide the correct syntax for conditionally setting padding based on the length? Thank you. #sample code <d ...

What causes the function endpoint to become unreachable when a throw is used?

One practical application of the never type in typescript occurs when a function has an endpoint that is never reached. However, I'm unsure why the throw statement specifically results in this unreachable endpoint. function error(message: string): ne ...

What is the best way to transfer the API Response Time from one Fetch function to a separate asynchronous function?

After successfully obtaining the API Response Time (duration) using the 'makeAPICall' function, I am now faced with the task of passing this duration variable value to another asynchronous function. Can anyone assist me in finding a solution to a ...

Issue: The inject() function can only be executed within an injection context. Issue: An untruthy value was expected to be truth

I'm currently in the process of setting up a unit test for my app.component. I've imported all the necessary components, but I keep encountering an error that's puzzling me. I activated "preserveSymlinks": true in my angular.json file, but t ...

How to incorporate a custom JavaScript file into an Angular 7 application

Suppose there is a JavaScript file named mylib.js in an angular 7 application, located at assets/mylib.js: mylib = function(){ return { hi: function() { alert('hi'); } }; }(); If I want to be able to call mylib.hi() in my hero-f ...

Encountering issues during the installation of JSNLog

Encountering some challenges with the installation and utilization of jsnlog following the instructions provided on the documentation. Continuously encountering errors when attempting to import or use the library. Attempted importing the library as outlin ...

Variable Scope is not defined in the TypeScript controller class of an AngularJS directive

I have implemented a custom directive to wrap ag grid like so: function MyDirective(): ng.IDirective { var directive = <ng.IDirective>{ restrict: "E", template: '<div style="width: 100%; height: 400px;" ag-grid="vm.agGrid ...

The correct procedure for refreshing a page in Angular 8

Note: I found some code snippets online but, after testing them out, I have doubts about their reliability due to inconsistencies. In my script, I have developed two utility functions - one for moving to the parent node and another for reloading the curre ...

Using Angular 2 to efficiently recycle a subcomponent with the same form across multiple parent components while maintaining its state

I've been struggling to find someone with the same issue as me, even though the title may seem familiar. Perhaps I need help rephrasing my question? Here's an explanation: Use case: I have multiple routes (only 2 in the example below) set up fo ...

What is the best way to integrate ng-bootstrap into an nx mono repository?

Encountering an issue when attempting to install ng-bootstrap in a multi-project mono repo using Nx. The error message received is as follows: npx ng add @ng-bootstrap/ng-bootstrap --project=web-app The add command needs to be executed in an Angular proje ...

Is it possible for Angular Components to be dynamically generated based on a parameter?

Is it feasible to achieve the following functionality in Angular? I am interested in creating multiple components that share a common base interface of properties; for instance, a string component, a date component, and an integer component, each with uni ...

Alternating the main access point from a separate module

I'm finding it difficult to understand why this should be so simple, but I just can't seem to solve this issue. Within my application, I have various root routes like login, events, and more. To manage the main menu functionality, I created a mo ...

Error: Unexpected input detected in `ts.resolveTypeReferenceDirective`. This issue may lead to a debug failure

I'm encountering the error below: { "name": "Angular", "version": "1.0.0", ... } If anyone has insights on what steps to take next or the potential cause of the problem, your input would be greatly a ...

The index type 'X' is not allowed for use in this scenario

I encountered an issue in my TypeScript code: error message: 'Type 'TransitionStyles' cannot be used as an index type.' I'm wondering if there's a way to modify my interface so that it can be used as an index type as well: ...

Angular 4 incorporates ES2017 features such as string.prototype.padStart to enhance functionality

I am currently working with Angular 4 and developing a string pipe to add zeros for padding. However, both Angular and VS Code are displaying errors stating that the prototype "padStart" does not exist. What steps can I take to enable this support in m ...

Implementing Basic Authentication for HTTP Requests in Angular 7

Currently, I am working with angular 7 along with Spring Boot and Spring Security. Within the Back End, I have successfully implemented basic authentication. However, while attempting to send a request from Angular with an Http Header that includes User n ...

The Error message "Property 'data' is not present in Type <void> | AxiosHttpResponse<any>" is indicating that the data property is missing on

When I fetch data for a specific user, I have a promise that I use to setState. Below is the implementation: getUserUsername = (): string => { const { match } = this.props; return match.params.username; }; onFetchUser = () => getUse ...