From 5866a59ed920fdbdba04550e74ae67b4f4f0f2fb Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 14:18:57 +0300 Subject: [PATCH 1/9] add a service for getting navigation events stream --- .../packages/core/src/lib/services/index.ts | 1 + .../lib/services/navigation-events.service.ts | 36 +++++++++ .../tests/navigation-events.service.spec.ts | 75 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index 37bc78d606..7044831681 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -9,6 +9,7 @@ export * from './lazy-load.service'; export * from './list.service'; export * from './localization.service'; export * from './multi-tenancy.service'; +export * from './navigation-events.service'; export * from './permission.service'; export * from './profile-state.service'; export * from './profile.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts new file mode 100644 index 0000000000..ff18b65ed3 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts @@ -0,0 +1,36 @@ +import { Injectable, Type } from '@angular/core'; +import { + NavigationCancel, + NavigationEnd, + NavigationError, + NavigationStart, + Router, + RouterEvent, +} from '@angular/router'; +import { filter } from 'rxjs/operators'; + +export type NavigationEventType = 'Cancel' | 'End' | 'Error' | 'Start'; + +@Injectable({ providedIn: 'root' }) +export class NavigationEvents { + private eventTypes = new Map>([ + ['Cancel', NavigationCancel], + ['End', NavigationEnd], + ['Error', NavigationError], + ['Start', NavigationStart], + ]); + + constructor(private router: Router) {} + + getOneOf(...eventTypes: NavigationEventType[]) { + return this.router.events.pipe( + filter((event: RouterEvent) => + eventTypes.some(type => event instanceof this.eventTypes.get(type)), + ), + ); + } + + getAny() { + return this.getOneOf(...this.eventTypes.keys()); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts new file mode 100644 index 0000000000..cf6d2992d0 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts @@ -0,0 +1,75 @@ +import { + NavigationCancel, + NavigationEnd, + NavigationError, + NavigationStart, + Router, + RouterEvent, +} from '@angular/router'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { Subject } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { NavigationEvents } from '../services/navigation-events.service'; + +describe('NavigationEvents', () => { + let spectator: SpectatorService; + let service: NavigationEvents; + const events = new Subject(); + + const createService = createServiceFactory({ + service: NavigationEvents, + providers: [ + { + provide: Router, + useValue: { events }, + }, + ], + }); + + beforeEach(() => { + spectator = createService(); + service = spectator.service; + }); + + describe('getOneOf', () => { + test.each` + filtered | expected + ${['Start', 'Cancel']} | ${[0, 3]} + ${['Error', 'Cancel']} | ${[0, 1]} + ${['Start', 'End']} | ${[2, 3]} + ${['Error', 'End']} | ${[1, 2]} + `('should return a stream of given navigation events', ({ filtered, expected }) => { + const stream = service.getOneOf(...filtered); + const collected: number[] = []; + + stream.pipe(take(2)).subscribe(event => collected.push(event.id)); + + events.next(new NavigationCancel(0, null, null)); + events.next(new NavigationError(1, null, null)); + events.next(new NavigationEnd(2, null, null)); + events.next(new NavigationStart(3, null, null)); + + expect(collected).toEqual(expected); + }); + }); + + describe('getAny', () => { + it('should return a stream of any navigation event', () => { + const stream = service.getAny(); + const collected: number[] = []; + + stream.pipe(take(4)).subscribe(event => collected.push(event.id)); + + events.next(new RouterEvent(0, null)); + events.next(new NavigationStart(1, null, null)); + events.next(new RouterEvent(2, null)); + events.next(new RouterEvent(3, null)); + events.next(new NavigationError(4, null, null)); + events.next(new NavigationEnd(5, null, null)); + events.next(new RouterEvent(6, null)); + events.next(new NavigationCancel(7, null, null)); + + expect(collected).toEqual([1, 4, 5, 7]); + }); + }); +}); From 5c5ca4e02b89fea8def3def420d4b7cafd1ece09 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 14:19:20 +0300 Subject: [PATCH 2/9] use NavigationEvents in RouterWaitService --- .../src/lib/services/router-wait.service.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts index 56240d8ca2..b11a1a298d 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts @@ -1,9 +1,10 @@ import { Injectable, Injector } from '@angular/core'; -import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; -import { filter, map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators'; -import { InternalStore } from '../utils/internal-store-utils'; +import { NavigationStart } from '@angular/router'; import { of, Subject, timer } from 'rxjs'; +import { map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators'; import { LOADER_DELAY } from '../tokens/lodaer-delay.token'; +import { InternalStore } from '../utils/internal-store-utils'; +import { NavigationEvents } from './navigation-events.service'; export interface RouterWaitState { loading: boolean; @@ -16,17 +17,15 @@ export class RouterWaitService { private store = new InternalStore({ loading: false }); private destroy$ = new Subject(); private delay: number; - constructor(private router: Router, injector: Injector) { + constructor(private navigationEvents: NavigationEvents, injector: Injector) { this.delay = injector.get(LOADER_DELAY, 500); - this.router.events + this.updateLoadingStatusOnNavigationEvents(); + } + + private updateLoadingStatusOnNavigationEvents() { + this.navigationEvents + .getAny() .pipe( - filter( - event => - event instanceof NavigationStart || - event instanceof NavigationEnd || - event instanceof NavigationError || - event instanceof NavigationCancel, - ), map(event => event instanceof NavigationStart), switchMap(condition => condition From 1399778913a1c1feae04d4115a9ddf5b973a1007 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 21:30:37 +0300 Subject: [PATCH 3/9] convert navigation events to router events service --- .../packages/core/src/lib/services/index.ts | 2 +- .../lib/services/navigation-events.service.ts | 36 ------ .../src/lib/services/router-events.service.ts | 60 ++++++++++ .../tests/navigation-events.service.spec.ts | 75 ------------ .../lib/tests/router-events.service.spec.ts | 111 ++++++++++++++++++ 5 files changed, 172 insertions(+), 112 deletions(-) delete mode 100644 npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts create mode 100644 npm/ng-packs/packages/core/src/lib/services/router-events.service.ts delete mode 100644 npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index 7044831681..55000edde1 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -9,13 +9,13 @@ export * from './lazy-load.service'; export * from './list.service'; export * from './localization.service'; export * from './multi-tenancy.service'; -export * from './navigation-events.service'; export * from './permission.service'; export * from './profile-state.service'; export * from './profile.service'; export * from './replaceable-components.service'; export * from './resource-wait.service'; export * from './rest.service'; +export * from './router-events.service'; export * from './router-wait.service'; export * from './routes.service'; export * from './session-state.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts deleted file mode 100644 index ff18b65ed3..0000000000 --- a/npm/ng-packs/packages/core/src/lib/services/navigation-events.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable, Type } from '@angular/core'; -import { - NavigationCancel, - NavigationEnd, - NavigationError, - NavigationStart, - Router, - RouterEvent, -} from '@angular/router'; -import { filter } from 'rxjs/operators'; - -export type NavigationEventType = 'Cancel' | 'End' | 'Error' | 'Start'; - -@Injectable({ providedIn: 'root' }) -export class NavigationEvents { - private eventTypes = new Map>([ - ['Cancel', NavigationCancel], - ['End', NavigationEnd], - ['Error', NavigationError], - ['Start', NavigationStart], - ]); - - constructor(private router: Router) {} - - getOneOf(...eventTypes: NavigationEventType[]) { - return this.router.events.pipe( - filter((event: RouterEvent) => - eventTypes.some(type => event instanceof this.eventTypes.get(type)), - ), - ); - } - - getAny() { - return this.getOneOf(...this.eventTypes.keys()); - } -} diff --git a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts new file mode 100644 index 0000000000..22981c641c --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts @@ -0,0 +1,60 @@ +import { Injectable, Type } from '@angular/core'; +import { + NavigationCancel, + NavigationEnd, + NavigationError, + NavigationStart, + Router, + RouterEvent, +} from '@angular/router'; +import { filter } from 'rxjs/operators'; + +export const NavigationEvent = { + Cancel: NavigationCancel, + End: NavigationEnd, + Error: NavigationError, + Start: NavigationStart, +}; + +@Injectable({ providedIn: 'root' }) +export class RouterEvents { + constructor(private router: Router) {} + + getEvents(...eventTypes: T) { + type FilteredRouterEvent = T extends Type[] ? Ctor : never; + + const filterRouterEvents = (event: RouterEvent): event is FilteredRouterEvent => + eventTypes.some(type => event instanceof type); + + return this.router.events.pipe(filter(filterRouterEvents)); + } + + getNavigationEvents(...navigationEventKeys: T) { + type FilteredNavigationEvent = T extends (infer Key)[] + ? Key extends NavigationEventKey + ? InstanceType + : never + : never; + + const filterNavigationEvents = (event: RouterEvent): event is FilteredNavigationEvent => + navigationEventKeys.some(key => event instanceof NavigationEvent[key]); + + return this.router.events.pipe(filter(filterNavigationEvents)); + } + + getAnyEvent() { + return this.router.events; + } + + getAnyNavigationEvent() { + const keys = Object.keys(NavigationEvent) as NavigationEventKeys; + return this.getNavigationEvents(...keys); + } +} + +type RouterEventConstructors = [Type, ...Type[]]; + +type NavigationEventKeys = [NavigationEventKey, ...NavigationEventKey[]]; + +export type NavigationEventType = typeof NavigationEvent; +export type NavigationEventKey = keyof NavigationEventType; diff --git a/npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts deleted file mode 100644 index cf6d2992d0..0000000000 --- a/npm/ng-packs/packages/core/src/lib/tests/navigation-events.service.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - NavigationCancel, - NavigationEnd, - NavigationError, - NavigationStart, - Router, - RouterEvent, -} from '@angular/router'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { Subject } from 'rxjs'; -import { take } from 'rxjs/operators'; -import { NavigationEvents } from '../services/navigation-events.service'; - -describe('NavigationEvents', () => { - let spectator: SpectatorService; - let service: NavigationEvents; - const events = new Subject(); - - const createService = createServiceFactory({ - service: NavigationEvents, - providers: [ - { - provide: Router, - useValue: { events }, - }, - ], - }); - - beforeEach(() => { - spectator = createService(); - service = spectator.service; - }); - - describe('getOneOf', () => { - test.each` - filtered | expected - ${['Start', 'Cancel']} | ${[0, 3]} - ${['Error', 'Cancel']} | ${[0, 1]} - ${['Start', 'End']} | ${[2, 3]} - ${['Error', 'End']} | ${[1, 2]} - `('should return a stream of given navigation events', ({ filtered, expected }) => { - const stream = service.getOneOf(...filtered); - const collected: number[] = []; - - stream.pipe(take(2)).subscribe(event => collected.push(event.id)); - - events.next(new NavigationCancel(0, null, null)); - events.next(new NavigationError(1, null, null)); - events.next(new NavigationEnd(2, null, null)); - events.next(new NavigationStart(3, null, null)); - - expect(collected).toEqual(expected); - }); - }); - - describe('getAny', () => { - it('should return a stream of any navigation event', () => { - const stream = service.getAny(); - const collected: number[] = []; - - stream.pipe(take(4)).subscribe(event => collected.push(event.id)); - - events.next(new RouterEvent(0, null)); - events.next(new NavigationStart(1, null, null)); - events.next(new RouterEvent(2, null)); - events.next(new RouterEvent(3, null)); - events.next(new NavigationError(4, null, null)); - events.next(new NavigationEnd(5, null, null)); - events.next(new RouterEvent(6, null)); - events.next(new NavigationCancel(7, null, null)); - - expect(collected).toEqual([1, 4, 5, 7]); - }); - }); -}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts new file mode 100644 index 0000000000..7b6af4f3ee --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts @@ -0,0 +1,111 @@ +import { + NavigationCancel, + NavigationEnd, + NavigationError, + NavigationStart, + ResolveEnd, + ResolveStart, + Router, + RouterEvent, +} from '@angular/router'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { Subject } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { NavigationEventKey, RouterEvents } from '../services/router-events.service'; + +describe('RouterEvents', () => { + let spectator: SpectatorService; + let service: RouterEvents; + const events = new Subject(); + const emitRouterEvents = () => { + events.next(new RouterEvent(0, null)); + events.next(new NavigationStart(1, null, null)); + events.next(new ResolveStart(2, null, null, null)); + events.next(new RouterEvent(3, null)); + events.next(new NavigationError(4, null, null)); + events.next(new NavigationEnd(5, null, null)); + events.next(new ResolveEnd(6, null, null, null)); + events.next(new NavigationCancel(7, null, null)); + }; + + const createService = createServiceFactory({ + service: RouterEvents, + providers: [ + { + provide: Router, + useValue: { events }, + }, + ], + }); + + beforeEach(() => { + spectator = createService(); + service = spectator.service; + }); + + describe('getNavigationEvents', () => { + test.each` + filtered | expected + ${['Start', 'Cancel']} | ${[1, 7]} + ${['Error', 'Cancel']} | ${[4, 7]} + ${['Start', 'End']} | ${[1, 5]} + ${['Error', 'End']} | ${[4, 5]} + `( + 'should return a stream of given navigation events', + ({ filtered, expected }: NavigationEventTest) => { + const stream = service.getNavigationEvents(...filtered); + const collected: number[] = []; + + stream.pipe(take(2)).subscribe(event => collected.push(event.id)); + + emitRouterEvents(); + + expect(collected).toEqual(expected); + }, + ); + }); + + describe('getAnyNavigationEvent', () => { + it('should return a stream of any navigation event', () => { + const stream = service.getAnyNavigationEvent(); + const collected: number[] = []; + + stream.pipe(take(4)).subscribe(event => collected.push(event.id)); + + emitRouterEvents(); + + expect(collected).toEqual([1, 4, 5, 7]); + }); + }); + + describe('getEvents', () => { + it('should return a stream of given router events', () => { + const stream = service.getEvents(ResolveEnd, ResolveStart); + const collected: number[] = []; + + stream.pipe(take(2)).subscribe(event => collected.push(event.id)); + + emitRouterEvents(); + + expect(collected).toEqual([2, 6]); + }); + }); + + describe('getAnyEvent', () => { + it('should return a stream of any router event', () => { + const stream = service.getAnyEvent(); + const collected: number[] = []; + + stream.pipe(take(8)).subscribe((event: RouterEvent) => collected.push(event.id)); + + emitRouterEvents(); + + expect(collected).toEqual([0, 1, 2, 3, 4, 5, 6, 7]); + }); + }); +}); + +type NavigationEventTest = { + filtered: [NavigationEventKey, ...NavigationEventKey[]]; + expected: number[]; +}; From e7eb33549f72ae6a7e7caf0b3f05b71a8160569a Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 21:31:55 +0300 Subject: [PATCH 4/9] use RouterEvents in RouterWaitService --- .../packages/core/src/lib/services/router-wait.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts index b11a1a298d..10c0100997 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts @@ -4,7 +4,7 @@ import { of, Subject, timer } from 'rxjs'; import { map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators'; import { LOADER_DELAY } from '../tokens/lodaer-delay.token'; import { InternalStore } from '../utils/internal-store-utils'; -import { NavigationEvents } from './navigation-events.service'; +import { RouterEvents } from './router-events.service'; export interface RouterWaitState { loading: boolean; @@ -17,14 +17,14 @@ export class RouterWaitService { private store = new InternalStore({ loading: false }); private destroy$ = new Subject(); private delay: number; - constructor(private navigationEvents: NavigationEvents, injector: Injector) { + constructor(private routerEvents: RouterEvents, injector: Injector) { this.delay = injector.get(LOADER_DELAY, 500); this.updateLoadingStatusOnNavigationEvents(); } private updateLoadingStatusOnNavigationEvents() { - this.navigationEvents - .getAny() + this.routerEvents + .getAnyNavigationEvent() .pipe( map(event => event instanceof NavigationStart), switchMap(condition => From ec84273b3c272aef2525c89a18b0798ea4bf2c17 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 21:47:15 +0300 Subject: [PATCH 5/9] use RouterEvents instead of filtering router events --- .../components/dynamic-layout.component.ts | 17 +++++++------- .../breadcrumb/breadcrumb.component.ts | 17 +++++++++----- .../src/lib/handlers/error.handler.ts | 22 ++++++++----------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 87ad976491..c9a852ae08 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,11 +1,11 @@ import { Component, Injector, Optional, SkipSelf, Type } from '@angular/core'; -import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; -import { filter } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; import { ReplaceableComponents } from '../models/replaceable-components'; import { LocalizationService } from '../services/localization.service'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; +import { RouterEvents } from '../services/router-events.service'; import { RoutesService } from '../services/routes.service'; import { SubscriptionService } from '../services/subscription.service'; import { findRoute, getRoutePath } from '../utils/route-utils'; @@ -44,6 +44,7 @@ export class DynamicLayoutComponent { private localizationService: LocalizationService, private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, + private routerEvents: RouterEvents, @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, ) { if (dynamicLayoutComponent) return; @@ -52,16 +53,16 @@ export class DynamicLayoutComponent { this.routes = injector.get(RoutesService); this.getLayout(); - this.subscription.addOne( - this.router.events.pipe(filter(event => event instanceof NavigationEnd)), - () => { - this.getLayout(); - }, - ); + this.checkLayoutOnNavigationEnd(); this.listenToLanguageChange(); } + private checkLayoutOnNavigationEnd() { + const navigationEnd$ = this.routerEvents.getNavigationEvents('End'); + this.subscription.addOne(navigationEnd$, () => this.getLayout()); + } + private getLayout() { let expectedLayout = (this.route.snapshot.data || {}).layout; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts index 11270c6dc1..441dc33ec8 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts @@ -1,7 +1,14 @@ -import { ABP, getRoutePath, RoutesService, TreeNode, SubscriptionService } from '@abp/ng.core'; +import { + ABP, + getRoutePath, + NavigationEvents, + RoutesService, + SubscriptionService, + TreeNode, +} from '@abp/ng.core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; -import { NavigationEnd, Router } from '@angular/router'; -import { filter, map, startWith } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { map, startWith } from 'rxjs/operators'; import { eThemeSharedRouteNames } from '../../enums'; @Component({ @@ -18,12 +25,12 @@ export class BreadcrumbComponent implements OnInit { private router: Router, private routes: RoutesService, private subscription: SubscriptionService, + private navigationEvents: NavigationEvents, ) {} ngOnInit(): void { this.subscription.addOne( - this.router.events.pipe( - filter(event => event instanceof NavigationEnd), + this.navigationEvents.getOneOf('End').pipe( // tslint:disable-next-line:deprecation startWith(null), map(() => this.routes.search({ path: getRoutePath(this.router) })), diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 97f9533cf7..1835b80eef 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -1,4 +1,4 @@ -import { AuthService, LocalizationParam, RestOccurError } from '@abp/ng.core'; +import { AuthService, LocalizationParam, RestOccurError, RouterEvents } from '@abp/ng.core'; import { HttpErrorResponse } from '@angular/common/http'; import { ApplicationRef, @@ -10,7 +10,7 @@ import { Injector, RendererFactory2, } from '@angular/core'; -import { NavigationError, ResolveEnd, Router } from '@angular/router'; +import { NavigationError, ResolveEnd } from '@angular/router'; import { Actions, ofActionSuccessful } from '@ngxs/store'; import { Observable, Subject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; @@ -72,7 +72,7 @@ export class ErrorHandler { constructor( private actions: Actions, - private router: Router, + private routerEvents: RouterEvents, private confirmationService: ConfirmationService, private cfRes: ComponentFactoryResolver, private rendererFactory: RendererFactory2, @@ -85,20 +85,16 @@ export class ErrorHandler { } private listenToRouterError() { - this.router.events - .pipe( - filter(event => event instanceof NavigationError), - filter(this.filterRouteErrors), - ) + this.routerEvents + .getNavigationEvents('Error') + .pipe(filter(this.filterRouteErrors)) .subscribe(() => this.show404Page()); } private listenToRouterDataResolved() { - this.router.events - .pipe( - filter(event => event instanceof ResolveEnd), - filter(() => !!this.componentRef), - ) + this.routerEvents + .getEvents(ResolveEnd) + .pipe(filter(() => !!this.componentRef)) .subscribe(() => { this.componentRef.destroy(); this.componentRef = null; From 2778998c4a464e4434ce0ee06267a1646af39800 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 21:57:34 +0300 Subject: [PATCH 6/9] use interface to avoid lint error --- .../packages/core/src/lib/tests/router-events.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts index 7b6af4f3ee..2fff89fd88 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts @@ -105,7 +105,7 @@ describe('RouterEvents', () => { }); }); -type NavigationEventTest = { +interface NavigationEventTest { filtered: [NavigationEventKey, ...NavigationEventKey[]]; expected: number[]; -}; +} From c608fa85df3261fa834a826a87e8d24482c9499a Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 23:07:47 +0300 Subject: [PATCH 7/9] rename methods that get all router/navigation events --- .../core/src/lib/services/router-events.service.ts | 7 ++++--- .../packages/core/src/lib/services/router-wait.service.ts | 2 +- .../core/src/lib/tests/router-events.service.spec.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts index 22981c641c..9acee09aa7 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts @@ -42,11 +42,11 @@ export class RouterEvents { return this.router.events.pipe(filter(filterNavigationEvents)); } - getAnyEvent() { + getAllEvents() { return this.router.events; } - getAnyNavigationEvent() { + getAllNavigationEvents() { const keys = Object.keys(NavigationEvent) as NavigationEventKeys; return this.getNavigationEvents(...keys); } @@ -56,5 +56,6 @@ type RouterEventConstructors = [Type, ...Type[]]; type NavigationEventKeys = [NavigationEventKey, ...NavigationEventKey[]]; -export type NavigationEventType = typeof NavigationEvent; +type NavigationEventType = typeof NavigationEvent; + export type NavigationEventKey = keyof NavigationEventType; diff --git a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts index 10c0100997..e53ca3a1b1 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts @@ -24,7 +24,7 @@ export class RouterWaitService { private updateLoadingStatusOnNavigationEvents() { this.routerEvents - .getAnyNavigationEvent() + .getAllNavigationEvents() .pipe( map(event => event instanceof NavigationStart), switchMap(condition => diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts index 2fff89fd88..0226c09d1e 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts @@ -67,7 +67,7 @@ describe('RouterEvents', () => { describe('getAnyNavigationEvent', () => { it('should return a stream of any navigation event', () => { - const stream = service.getAnyNavigationEvent(); + const stream = service.getAllNavigationEvents(); const collected: number[] = []; stream.pipe(take(4)).subscribe(event => collected.push(event.id)); @@ -93,7 +93,7 @@ describe('RouterEvents', () => { describe('getAnyEvent', () => { it('should return a stream of any router event', () => { - const stream = service.getAnyEvent(); + const stream = service.getAllEvents(); const collected: number[] = []; stream.pipe(take(8)).subscribe((event: RouterEvent) => collected.push(event.id)); From 531ed0f733380a819ec834233de2d2b954db6f6b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 23:34:47 +0300 Subject: [PATCH 8/9] add documentation for router events --- docs/en/UI/Angular/Router-Events.md | 146 ++++++++++++++++++++++++++++ docs/en/docs-nav.json | 4 + 2 files changed, 150 insertions(+) create mode 100644 docs/en/UI/Angular/Router-Events.md diff --git a/docs/en/UI/Angular/Router-Events.md b/docs/en/UI/Angular/Router-Events.md new file mode 100644 index 0000000000..011d405b22 --- /dev/null +++ b/docs/en/UI/Angular/Router-Events.md @@ -0,0 +1,146 @@ +# Router Events Simplified + +`RouterEvents` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose. + + + + +## Benefit + +You can use router events directly and filter them as seen below: + +```js +import { + NavigationEnd, + NavigationError, + NavigationCancel, + Router, +} from '@angular/router'; +import { filter } from 'rxjs/operators'; + +@Injectable() +class SomeService { + navigationFinish$ = this.router.events.pipe( + filter( + event => + event instanceof NavigationEnd || + event instanceof NavigationError || + event instanceof NavigationCancel, + ), + ); + /* Observable */ + + constructor(private router: Router) {} +} +``` + +However, `RouterEvents` makes filtering router events easier. + +```js +import { RouterEvents } from '@abp/ng.core'; + +@Injectable() +class SomeService { + navigationFinish$ = this.routerEvents.getNavigationEvents('End', 'Error', 'Cancel'); + /* Observable */ + + constructor(private routerEvents: RouterEvents) {} +} +``` + +`RouterEvents` also delivers improved type-safety. In the example above, `navigationFinish$` has inferred type of `Observable` whereas it would have `Observable` when router events are filtered directly. + + + + +## Usage + +You do not have to provide `RouterEvents` at the module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components. + + +### How to Get Specific Navigation Events + +You can use `getNavigationEvents` to get a stream of navigation events matching given event keys. + +```js +import { RouterEvents } from '@abp/ng.core'; +import { merge } from 'rxjs'; +import { mapTo } from 'rxjs/operators'; + +@Injectable() +class SomeService { + navigationStart$ = this.routerEvents.getNavigationEvents('Start'); + /* Observable */ + + navigationFinish$ = this.routerEvents.getNavigationEvents('End', 'Error', 'Cancel'); + /* Observable */ + + loading$ = merge( + this.navigationStart$.pipe(mapTo(true)), + this.navigationFinish$.pipe(mapTo(false)), + ); + /* Observable */ + + constructor(private routerEvents: RouterEvents) {} +} +``` + + +### How to Get All Navigation Events + +You can use `getAllNavigationEvents` to get a stream of all navigation events without passing any keys. + +```js +import { RouterEvents, NavigationStart } from '@abp/ng.core'; +import { map } from 'rxjs/operators'; + +@Injectable() +class SomeService { + navigationEvent$ = this.routerEvents.getAllNavigationEvents(); + /* Observable */ + + loading$ = this.navigationEvent$.pipe( + map(event => event instanceof NavigationStart), + ); + /* Observable */ + + constructor(private routerEvents: RouterEvents) {} +} +``` + + +### How to Get Specific Router Events + +You can use `getEvents` to get a stream of router events matching given event constructors. + +```js +import { RouterEvents } from '@abp/ng.core'; +import { ActivationEnd, ChildActivationEnd } from '@angular/router'; + +@Injectable() +class SomeService { + moduleActivation$ = this.routerEvents.getEvents(ActivationEnd, ChildActivationEnd); + /* Observable */ + + constructor(private routerEvents: RouterEvents) {} +} +``` + + +### How to Get All Router Events + +You can use `getEvents` to get a stream of all router events without passing any event constructors. This is nothing different from accessing `events` property of `Router` and is added to the service just for convenience. + +```js +import { RouterEvents } from '@abp/ng.core'; +import { ActivationEnd, ChildActivationEnd } from '@angular/router'; + +@Injectable() +class SomeService { + routerEvent$ = this.routerEvents.getAllEvents(); + /* Observable */ + + constructor(private routerEvents: RouterEvents) {} +} +``` + diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 06594f8e7e..7fd7762ad4 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -807,6 +807,10 @@ "text": "Easy *ngFor trackBy", "path": "UI/Angular/Track-By-Service.md" }, + { + "text": "Router Events", + "path": "UI/Angular/Router-Events.md" + }, { "text": "Inserting Scripts & Styles to DOM", "path": "UI/Angular/Dom-Insertion-Service.md" From 36eb7f9467bec6932c2bb3bb11ea044bba0fd7fd Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 Jan 2021 23:45:43 +0300 Subject: [PATCH 9/9] use RouterEvents instead of NavigationEvents --- .../src/lib/components/breadcrumb/breadcrumb.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts index 441dc33ec8..65c3c0a0af 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts @@ -1,7 +1,7 @@ import { ABP, getRoutePath, - NavigationEvents, + RouterEvents, RoutesService, SubscriptionService, TreeNode, @@ -25,12 +25,12 @@ export class BreadcrumbComponent implements OnInit { private router: Router, private routes: RoutesService, private subscription: SubscriptionService, - private navigationEvents: NavigationEvents, + private routerEvents: RouterEvents, ) {} ngOnInit(): void { this.subscription.addOne( - this.navigationEvents.getOneOf('End').pipe( + this.routerEvents.getNavigationEvents('End').pipe( // tslint:disable-next-line:deprecation startWith(null), map(() => this.routes.search({ path: getRoutePath(this.router) })),