From 940afbe796a06b4e0e162cad7a4cf9f1463c70db Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Wed, 9 Nov 2022 21:21:04 +0530 Subject: [PATCH 1/5] breaking: add required `forRoot` method --- .../src/lib/ngx-local-storage.module.ts | 64 ++++++++++++++++--- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/projects/ngx-lss/src/lib/ngx-local-storage.module.ts b/projects/ngx-lss/src/lib/ngx-local-storage.module.ts index dfe487f..60cf718 100644 --- a/projects/ngx-lss/src/lib/ngx-local-storage.module.ts +++ b/projects/ngx-lss/src/lib/ngx-local-storage.module.ts @@ -1,12 +1,60 @@ -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('ROUTER_FORROOT_GUARD'); + +type GuardedResult = boolean; + +const MULTIPLE_FORROT_IMPORTS_ERROR = + `The NgxLocalStorageModule was provided more than once. This could happen when '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 provided 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 { + 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; +} From 0935535209f2da51400dc20523fa1643c925c3fc Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Wed, 9 Nov 2022 21:37:44 +0530 Subject: [PATCH 2/5] chore: cleanup imports --- projects/ngx-lss/src/lib/ngx-local-storage.module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/ngx-lss/src/lib/ngx-local-storage.module.ts b/projects/ngx-lss/src/lib/ngx-local-storage.module.ts index 60cf718..d098b61 100644 --- a/projects/ngx-lss/src/lib/ngx-local-storage.module.ts +++ b/projects/ngx-lss/src/lib/ngx-local-storage.module.ts @@ -1,4 +1,3 @@ -import { CommonModule } from '@angular/common'; import { Inject, InjectionToken, ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'; import { NgxLocalStorageService } from './ngx-local-storage.service'; From 01d5c1d832755f1d574c8098de645c5275af1885 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Wed, 9 Nov 2022 21:21:57 +0530 Subject: [PATCH 3/5] test: add tests for module initialization --- .../src/lib/ngx-local-storage.module.spec.ts | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 projects/ngx-lss/src/lib/ngx-local-storage.module.spec.ts diff --git a/projects/ngx-lss/src/lib/ngx-local-storage.module.spec.ts b/projects/ngx-lss/src/lib/ngx-local-storage.module.spec.ts new file mode 100644 index 0000000..4d95b74 --- /dev/null +++ b/projects/ngx-lss/src/lib/ngx-local-storage.module.spec.ts @@ -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: '' }) +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(); + })); + }); + }); +}); From 80f3c3d58b1ca3074494165cf63bf585fa5aac23 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Wed, 9 Nov 2022 21:22:31 +0530 Subject: [PATCH 4/5] chore: make jsdoc not required --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index 10dca14..4bd65f7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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", From 20d07eaad6ff4a7ad4eeda058eff987794af75a4 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub Date: Wed, 9 Nov 2022 21:44:47 +0530 Subject: [PATCH 5/5] docs: improve wording --- projects/ngx-lss/src/lib/ngx-local-storage.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/ngx-lss/src/lib/ngx-local-storage.module.ts b/projects/ngx-lss/src/lib/ngx-local-storage.module.ts index d098b61..deb2a83 100644 --- a/projects/ngx-lss/src/lib/ngx-local-storage.module.ts +++ b/projects/ngx-lss/src/lib/ngx-local-storage.module.ts @@ -7,12 +7,12 @@ export const FORROOT_GUARD_TOKEN = new InjectionToken('ROUTER_FORROOT_GUAR type GuardedResult = boolean; const MULTIPLE_FORROT_IMPORTS_ERROR = - `The NgxLocalStorageModule was provided more than once. This could happen when 'forRoot' is used outside of the root injector.`; + `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 provided once via static `forRoot()` method in order to prevent possible issues with multiple instances of the storage 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 {