Skip to content
This repository has been archived by the owner on Jul 29, 2023. It is now read-only.

Commit

Permalink
Merge pull request #5 from chernodub/feature/prevent-multiple-injections
Browse files Browse the repository at this point in the history
  • Loading branch information
chernodub authored Nov 9, 2022
2 parents 138a6b6 + 20d07ea commit 1303fef
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 9 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"variables": true
}
],
"jsdoc/require-jsdoc": "off",
"rxjs/no-ignored-replay-buffer": "error",
"rxjs/no-internal": "error",
"rxjs/no-nested-subscribe": "error",
Expand Down
81 changes: 81 additions & 0 deletions projects/ngx-lss/src/lib/ngx-local-storage.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Component, NgModule } from '@angular/core';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router, RouterModule } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';

import { NgxLocalStorageModule } from './ngx-local-storage.module';
import { NgxLocalStorageService } from './ngx-local-storage.service';

@Component({ template: '<router-outlet></router-outlet>' })
export class DummyRootComponent {}

@Component({ template: '' })
export class LazyDummyComponent {}

@NgModule({
declarations: [],
imports: [
NgxLocalStorageModule.forRoot(),
RouterModule.forChild([{ path: '', pathMatch: 'full', component: LazyDummyComponent }]),
],
})
export class LazyDummyModule {}

describe('NgxLocalStorageModule', () => {
describe('without `forRoot` call', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxLocalStorageModule],
});
});

it('throws an error', () => {
expect(() => TestBed.inject(NgxLocalStorageService)).toThrow()
});
});

describe('with `forRoot` call', () => {
describe('when imported once', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxLocalStorageModule.forRoot()],
});
});

it('injects service', () => {
expect(TestBed.inject(NgxLocalStorageService)).toBeTruthy();
});
});

describe('when imported more than once', () => {
let router: Router;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [DummyRootComponent],
imports: [
NgxLocalStorageModule.forRoot(),
RouterTestingModule.withRoutes([
{
path: '',
pathMatch: 'full',
loadChildren: () => Promise.resolve(LazyDummyModule),
},
]),
],
});

router = TestBed.inject(Router);
});

// Using `fakeAsync()` and `tick()` to wait for the router to initialize the lazy module
it('throws an error on module initialization', fakeAsync(() => {

expect(() => {
router.initialNavigation();
tick();
}).toThrowError();
}));
});
});
});
65 changes: 56 additions & 9 deletions projects/ngx-lss/src/lib/ngx-local-storage.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,59 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Inject, InjectionToken, ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';

import { NgxLocalStorageService } from './ngx-local-storage.service';

/** Module providing local storage adapter service. */
@NgModule({
declarations: [],
imports: [CommonModule],
providers: [NgxLocalStorageService],
})
export class NgxLocalStorageModule { }
export const FORROOT_GUARD_TOKEN = new InjectionToken<void>('ROUTER_FORROOT_GUARD');

type GuardedResult = boolean;

const MULTIPLE_FORROT_IMPORTS_ERROR =
`The NgxLocalStorageModule was imported more than once. This could happen if 'forRoot' is used outside of the root injector.`;
const INITIALIZED_WITHOUT_FORROOT_ERROR = `NgxLocalStorageModule must be imported via 'forRoot()' call in your root module.`;

/**
* Module providing local storage adapter service.
* Must be imported once via static `forRoot()` method in order to prevent possible issues with multiple instances of the storage service.
*/
@NgModule()
export class NgxLocalStorageModule {

public constructor(
@Optional() @Inject(FORROOT_GUARD_TOKEN) guarded: GuardedResult,
) {
if (!guarded) {
throw new Error(INITIALIZED_WITHOUT_FORROOT_ERROR);
}
}

/**
* Initializes the module for root.
*/
public static forRoot(): ModuleWithProviders<NgxLocalStorageModule> {
return {
ngModule: NgxLocalStorageModule,
providers: [
NgxLocalStorageService,

// Borrowed the idea from
// https://github.com/angular/angular/blob/main/packages/router/src/router_module.ts#L179
{
provide: FORROOT_GUARD_TOKEN,
useFactory: provideForRootGuard,
deps: [[NgxLocalStorageService, new Optional(), new SkipSelf()]],
},
],
};
}

}

/**
* Makes sure that the module is only injected once.
* @param service Service instance.
*/
export function provideForRootGuard(service: NgxLocalStorageService): GuardedResult {
if (service) {
throw new Error(MULTIPLE_FORROT_IMPORTS_ERROR);
}
return true;
}

0 comments on commit 1303fef

Please sign in to comment.