Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular library module inject service with abstract class

Tags:

I have created an Angular Component Library, which I distribute via NPM (over Nexus) to several similar projects. This contains a PageComponent, which in turn contains a FooterComponent and a NavbarComponent. In NavbarComponent exists a button, which triggers a logout function. This function is to be provided via a PageService of the respective project. For this purpose I created an AbstractPageService in the Angular Component library (PageService extends AbstractPageService).

At first I solved this via the EventEmitter. But since I had to provide a logout function for each new page, I wanted to solve this via one service per project. I pass the PageService (Project) with using the forRoot() method of Angular Component Library.

Everything works as desired, but wanted to know if there is a better solution or if the solution is so recommendable at all?

I have the following solution for this:

Components Lib - components.module.ts

import {ModuleWithProviders, NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';


import {NavbarComponent} from './layout/navbar/navbar.component';
import {PageComponent} from './layout/page/page.component';
import {PageHeaderComponent} from './components/page-header/page-header.component';
// ... others ...

@NgModule({
  imports: [
    CommonModule,
    RouterModule,
    FontAwesomeModule
  ],
  declarations: [
    NavbarComponent,
    PageComponent,
    // ... others ...
  ],
  exports: [
    NavbarComponent,
    PageComponent,
    // ... others ...
  ]
})
export class ComponentsModule {
  static forRoot(pageService): ModuleWithProviders {
    return {
      ngModule: ComponentsModule,
      providers: [
        {provide: 'PageService', useClass: pageService}
      ]
    };
  }
}

Component Lib - page.component.ts

import {Component, EventEmitter, HostBinding, Inject, Input, Output} from '@angular/core';
import {AbstractPageService} from '../../services/abstract-page.service';

@Component({
  selector: 'dc-page',
  templateUrl: './page.component.html',
  styleUrls: ['./page.component.scss']
})
export class PageComponent {

  @HostBinding('class') styleClass = 'd-flex flex-column';

  @Input() customStyleClass = null;

  @Input() showLogoutButton = true;
  // @Output() logoutButtonClick: EventEmitter<any> = new EventEmitter();

  constructor(@Inject('PageService') protected pageService: AbstractPageService) {
  }

  logout(): void {
    this.pageService.logout();
  }
}

Component Lib - abstract-page.service.ts

import {Injectable} from '@angular/core';

@Injectable()
export abstract class AbstractPageService {

  abstract logout(): void;

}

And here the use in a project:

Project - app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

import {AppComponent} from './app.component';
import {RouterModule, Routes} from '@angular/router';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';

import {ComponentsModule} from 'components';

const appRoutes: Routes = [
  {path: '', component: AppComponent},

  // otherwise redirect to home
  {path: '**', redirectTo: ''}
];


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    FontAwesomeModule,
    RouterModule.forRoot(appRoutes),
    ComponentsModule.forRoot(PageService),
  ],
  providers: [
    // {provide: 'PageService', useClass: PageService}
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Project - page.service.ts

import {Injectable} from '@angular/core';
import {AbstractPageService} from '../../../projects/components/src/lib/services/abstract-page.service';

@Injectable({
  providedIn: 'root'
})
export class PageService extends AbstractPageService {

  constructor() {
    super();
  }

  logout() {
    console.log('Ausloggen!');
  }
}
like image 440
William Avatar asked Aug 15 '18 12:08

William


1 Answers

I used this approach but ran into one problem:

The PageService is not the same singleton instance in the library as in the application. Because it is provided in multiple modules (application module and the library module), it creates a new instance for each module.

What I ended up doing was providing my service through a string in the application module:

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

Then injecting the service where needed using @Inject()

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

Then, in the library, I created a simple interface:

export interface AbstractPageService {
  logout(): void;
}

Then, I can simply inject the service in the library through the Inject() decorator and type it using the interface (without the need to implement the interface in the root application):

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

Now both the root application and the library use the same singleton instance of the PageService, as provided in the root application.

like image 187
Jesse Avatar answered Oct 05 '22 01:10

Jesse