diff --git a/libs/components/router/src/lib/modules/href/fixtures/href-resolver-fixture.service.ts b/libs/components/router/src/lib/modules/href/fixtures/href-resolver-fixture.service.ts index f4aa435560..c6b23e52dc 100644 --- a/libs/components/router/src/lib/modules/href/fixtures/href-resolver-fixture.service.ts +++ b/libs/components/router/src/lib/modules/href/fixtures/href-resolver-fixture.service.ts @@ -39,6 +39,8 @@ export class HrefResolverFixtureService implements SkyHrefResolver { }); } else if (url.startsWith('error://')) { throw new Error(`Error while resolving ${url}`); + } else if (url.startsWith('reject://')) { + return Promise.reject(`Unable to resolve ${url}`); } else { return Promise.resolve({ url, diff --git a/libs/components/router/src/lib/modules/href/href.directive.spec.ts b/libs/components/router/src/lib/modules/href/href.directive.spec.ts index db5fa4eb82..a2544d6a90 100644 --- a/libs/components/router/src/lib/modules/href/href.directive.spec.ts +++ b/libs/components/router/src/lib/modules/href/href.directive.spec.ts @@ -290,6 +290,18 @@ describe('SkyHref Directive', () => { tick(); expect(element?.getAttribute('hidden')).toBeNull(); + + fixture.componentInstance.dynamicLink = 'reject://simple-app/example/page'; + fixture.detectChanges(); + tick(); + + expect(element?.getAttribute('hidden')).toBe('hidden'); + + fixture.componentInstance.dynamicLink = '1bb-nav://simple-app/fixed'; + fixture.detectChanges(); + tick(); + + expect(element?.getAttribute('hidden')).toBeNull(); })); it('should handle the else parameter', fakeAsync(() => { diff --git a/libs/components/router/src/lib/modules/href/href.directive.ts b/libs/components/router/src/lib/modules/href/href.directive.ts index 013467385d..d3a69bf08e 100644 --- a/libs/components/router/src/lib/modules/href/href.directive.ts +++ b/libs/components/router/src/lib/modules/href/href.directive.ts @@ -1,18 +1,22 @@ import { ApplicationRef, ChangeDetectorRef, + DestroyRef, Directive, ElementRef, EventEmitter, HostListener, Input, - Optional, Output, Renderer2, + inject, } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Router, UrlTree } from '@angular/router'; import { SkyAppConfig, SkyAppRuntimeConfigParamsProvider } from '@skyux/config'; +import { Subscription, catchError, finalize, from, of } from 'rxjs'; + import { SkyHrefResolverService } from './href-resolver.service'; import { SkyHref } from './types/href'; import { SkyHrefChange } from './types/href-change'; @@ -41,7 +45,7 @@ export class SkyHrefDirective { this.#_skyHref = skyHref ? skyHref.join('/') : ''; } - void this.#checkRouteAccess(); + this.#checkRouteAccess(); } public get skyHref(): string { @@ -92,34 +96,18 @@ export class SkyHrefDirective { #_skyHref = ''; #_skyHrefElse: 'hide' | 'unlink' | undefined = 'hide'; - #router: Router; - #renderer: Renderer2; - #element: ElementRef; - #skyAppConfig: SkyAppConfig | undefined; - #paramsProvider: SkyAppRuntimeConfigParamsProvider | undefined; - #hrefResolver: SkyHrefResolverService | undefined; - #applicationRef: ApplicationRef | undefined; - #changeDetectorRef: ChangeDetectorRef | undefined; - - constructor( - router: Router, - renderer: Renderer2, - element: ElementRef, - @Optional() skyAppConfig?: SkyAppConfig, - @Optional() paramsProvider?: SkyAppRuntimeConfigParamsProvider, - @Optional() hrefResolver?: SkyHrefResolverService, - @Optional() applicationRef?: ApplicationRef, - @Optional() changeDetectorRef?: ChangeDetectorRef, - ) { - this.#router = router; - this.#renderer = renderer; - this.#element = element; - this.#skyAppConfig = skyAppConfig; - this.#paramsProvider = paramsProvider; - this.#hrefResolver = hrefResolver; - this.#applicationRef = applicationRef; - this.#changeDetectorRef = changeDetectorRef; - } + readonly #router = inject(Router); + readonly #renderer = inject(Renderer2); + readonly #element = inject(ElementRef); + readonly #skyAppConfig = inject(SkyAppConfig, { optional: true }); + readonly #paramsProvider = inject(SkyAppRuntimeConfigParamsProvider, { + optional: true, + }); + readonly #hrefResolver = inject(SkyHrefResolverService, { optional: true }); + readonly #applicationRef = inject(ApplicationRef); + readonly #changeDetectorRef = inject(ChangeDetectorRef); + readonly #destroyRef = inject(DestroyRef); + #checkingSubscription: Subscription | undefined; @HostListener('click', [ '$event.button', @@ -179,29 +167,37 @@ export class SkyHrefDirective { this.skyHrefChange.emit({ userHasAccess: !change.hidden }); } - async #checkRouteAccess(): Promise { + #checkRouteAccess(): void { this.#route = { url: this.skyHref, userHasAccess: false, }; - /* istanbul ignore else */ if (this.#hrefResolver && this.skyHref) { this.#applyChanges(this.#getChanges()); + this.#checkingSubscription?.unsubscribe(); try { - const route = await this.#hrefResolver.resolveHref({ - url: this.skyHref, - }); - - this.#route = { ...route }; - this.#applyChanges(this.#getChanges()); - - /* istanbul ignore else */ - if (this.#changeDetectorRef && this.#applicationRef) { - this.#changeDetectorRef.markForCheck(); - this.#applicationRef.tick(); - } + this.#checkingSubscription = from( + this.#hrefResolver.resolveHref({ url: this.skyHref }), + ) + .pipe( + catchError(() => + of({ + url: this.skyHref, + userHasAccess: false, + } as SkyHref), + ), + takeUntilDestroyed(this.#destroyRef), + finalize(() => { + this.#applyChanges(this.#getChanges()); + }), + ) + .subscribe((route) => { + this.#route = { ...route }; + this.#changeDetectorRef.markForCheck(); + this.#applicationRef.tick(); + }); } catch { - this.#applyChanges(this.#getChanges()); + // Unable to resolve. } } else { // no resolver or skyHref is falsy