diff --git a/README.md b/README.md index d1b2911f..9d3c5669 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ nx generate ngxtension:init - [connect](https://ngxtension.netlify.app/utilities/connect) - [create-effect](https://ngxtension.netlify.app/utilities/create-effect) - [if-validator](https://ngxtension.netlify.app/utilities/if-validator) +- [navigation-end](https://ngxtension.netlify.app/utilities/navigation-end) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index f1b0f2fc..56a7d686 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -51,6 +51,7 @@ export default defineConfig({ { label: 'createEffect', link: '/utilities/create-effect' }, { label: 'ifValidator', link: '/utilities/if-validator' }, { label: 'call apply Pipes', link: '/utilities/call-apply' }, + { label: 'navigationEnd', link: '/utilities/navigation-end' }, ], }, ], diff --git a/docs/src/content/docs/utilities/navigation-end.md b/docs/src/content/docs/utilities/navigation-end.md new file mode 100644 index 00000000..2b0f8d53 --- /dev/null +++ b/docs/src/content/docs/utilities/navigation-end.md @@ -0,0 +1,35 @@ +--- +title: injectNavigationEnd +description: ngxtension/navigation-end +--- + +The `injectNavigationEnd` function is a utility for creating an `Observable` that emits when a navigation ends. It might perform tasks after a route navigation has been completed. + +```ts +import { injectNavigationEnd } from 'ngxtension/navigation-end'; +``` + +## Usage + +`injectNavigationEnd` accepts optionally `Injector`. + +```ts +import { Component } from '@angular/core'; +import { injectNavigationEnd } from 'ngxtension/navigation-end'; +import { NavigationEnd } from '@angular/router'; + +@Component({ + standalone: true, + selector: 'app-example', + template: '

Example Component

', +}) +export class ExampleComponent { + navigationEnd$ = injectNavigationEnd(); + constructor() { + navigationEnd$.subscribe((event: NavigationEnd) => { + // This code will run when a navigation ends. + console.log('Navigation ended:', event); + }); + } +} +``` diff --git a/libs/ngxtension/navigation-end/README.md b/libs/ngxtension/navigation-end/README.md new file mode 100644 index 00000000..9f9ae86b --- /dev/null +++ b/libs/ngxtension/navigation-end/README.md @@ -0,0 +1,3 @@ +# ngxtension/navigation-end + +Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/navigation-end`. diff --git a/libs/ngxtension/navigation-end/ng-package.json b/libs/ngxtension/navigation-end/ng-package.json new file mode 100644 index 00000000..b3e53d69 --- /dev/null +++ b/libs/ngxtension/navigation-end/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/ngxtension/navigation-end/project.json b/libs/ngxtension/navigation-end/project.json new file mode 100644 index 00000000..90aeed00 --- /dev/null +++ b/libs/ngxtension/navigation-end/project.json @@ -0,0 +1,33 @@ +{ + "name": "ngxtension/navigation-end", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "libs/ngxtension/navigation-end/src", + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/ngxtension/jest.config.ts", + "testPathPattern": ["navigation-end"], + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "libs/ngxtension/navigation-end/**/*.ts", + "libs/ngxtension/navigation-end/**/*.html" + ] + } + } + } +} diff --git a/libs/ngxtension/navigation-end/src/index.ts b/libs/ngxtension/navigation-end/src/index.ts new file mode 100644 index 00000000..bd4244f1 --- /dev/null +++ b/libs/ngxtension/navigation-end/src/index.ts @@ -0,0 +1 @@ +export * from './navigation-end'; diff --git a/libs/ngxtension/navigation-end/src/navigation-end.spec.ts b/libs/ngxtension/navigation-end/src/navigation-end.spec.ts new file mode 100644 index 00000000..0ca3b274 --- /dev/null +++ b/libs/ngxtension/navigation-end/src/navigation-end.spec.ts @@ -0,0 +1,52 @@ +import { Component } from '@angular/core'; +import { + ComponentFixture, + TestBed, + fakeAsync, + tick, +} from '@angular/core/testing'; +import { NavigationEnd, Router } from '@angular/router'; +import { delay, of } from 'rxjs'; +import { injectNavigationEnd } from './navigation-end'; + +describe(injectNavigationEnd.name, () => { + @Component({ + standalone: true, + template: '', + }) + class Foo { + count = 0; + navigationEnd$ = injectNavigationEnd(); + + ngOnInit() { + this.navigationEnd$.pipe(delay(0)).subscribe(() => { + this.count = 1; + }); + } + } + + let component: Foo; + let fixture: ComponentFixture; + beforeEach(() => { + TestBed.overrideProvider(Router, { + useValue: { + events: of(new NavigationEnd(0, '', '')), + }, + }); + fixture = TestBed.createComponent(Foo); + fixture.autoDetectChanges(); + component = fixture.componentInstance; + }); + + it('should modify "count" when router NavigationEnd event occur', fakeAsync(() => { + component.ngOnInit(); + expect(component.count).toBe(0); + tick(100); + expect(component.count).toBe(1); + + fixture.destroy(); // destroy the component here + + tick(500); + expect(component.count).toBe(1); + })); +}); diff --git a/libs/ngxtension/navigation-end/src/navigation-end.ts b/libs/ngxtension/navigation-end/src/navigation-end.ts new file mode 100644 index 00000000..f6f20250 --- /dev/null +++ b/libs/ngxtension/navigation-end/src/navigation-end.ts @@ -0,0 +1,21 @@ +import { Injector, inject, runInInjectionContext } from '@angular/core'; +import { Event, NavigationEnd, Router } from '@angular/router'; +import { assertInjector } from 'ngxtension/assert-injector'; +import { filter, type Observable } from 'rxjs'; + +/** + * Creates an Observable that emits when a navigation ends. + * @returns An Observable of NavigationEnd events. + */ +export function injectNavigationEnd( + injector?: Injector +): Observable { + injector = assertInjector(injectNavigationEnd, injector); + return runInInjectionContext(injector, () => { + return inject(Router).events.pipe( + filter( + (event: Event): event is NavigationEnd => event instanceof NavigationEnd + ) + ); + }); +} diff --git a/tsconfig.base.json b/tsconfig.base.json index c89e51e3..1b508626 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -35,6 +35,9 @@ "ngxtension/inject-destroy": [ "libs/ngxtension/inject-destroy/src/index.ts" ], + "ngxtension/navigation-end": [ + "libs/ngxtension/navigation-end/src/index.ts" + ], "ngxtension/repeat": ["libs/ngxtension/repeat/src/index.ts"], "ngxtension/resize": ["libs/ngxtension/resize/src/index.ts"], "plugin": ["libs/plugin/src/index.ts"]