Angular library module inject service with abstrac

2020-07-06 07:09发布

问题:

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!');
  }
}

回答1:

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.



回答2:

As Sunil Singh said, a normal abstract class is the solution (without @Injectable).

export abstract class AbstractPageService {

  abstract logout(): void;

}


回答3:

I think there should be 'useValue' instead of 'useClass'.

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