diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3cb7904..28a9d90 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -8,8 +8,8 @@ import { TanStackRouterDevtoolsComponent } from '../router/router-devtools'; template: `

Welcome to {{ title }}!

- Home | About | - Parent 1 + Home | About | + Parent 1
diff --git a/src/app/parent.component.ts b/src/app/parent.component.ts index b7b62f6..ebbb386 100644 --- a/src/app/parent.component.ts +++ b/src/app/parent.component.ts @@ -20,9 +20,9 @@ export const Route = createRoute({ imports: [Outlet, Link], template: ` Parent - - Child | - Child 1 | - Child 2 + Child | + Child 1 | + Child 2
diff --git a/src/router/create-router.ts b/src/router/create-router.ts index c5a139a..95b01ae 100644 --- a/src/router/create-router.ts +++ b/src/router/create-router.ts @@ -108,7 +108,7 @@ export class NgRouter< > ) { super(options); - this.load({ sync: true }); + void this.load({ sync: true }); this.__store.subscribe(() => { this.routerState.set(this.state); }); diff --git a/src/router/link.ts b/src/router/link.ts index 2ca444b..dd18190 100644 --- a/src/router/link.ts +++ b/src/router/link.ts @@ -4,32 +4,27 @@ import { injectRouter } from './router'; @Directive({ selector: 'a[link]', + exportAs: 'link', host: { '(click)': 'navigate($event)', - '[attr.href]': '_href()', + '[attr.href]': 'hostHref()', }, }) export class Link { - to = input(); - from = input(); - params = input(); - search = input(); - hash = input(); - options = input(); + toOptions = input.required< + | (Omit & { to: NonNullable }) + | NonNullable + >({ alias: 'link' }); + router = injectRouter(); - private navigateOptions = computed(() => { - const options: NavigateOptions = { - to: this.to(), - from: this.from(), - params: this.params(), - search: this.search(), - hash: this.hash(), - ...this.options(), - }; - return options; + private navigateOptions = computed(() => { + const to = this.toOptions(); + if (typeof to === 'object') return to; + return { to }; }); - _href = computed( + + protected hostHref = computed( () => this.router.buildLocation(this.navigateOptions()).href ); diff --git a/src/router/outlet.ts b/src/router/outlet.ts index e7b61ea..f3db3ce 100644 --- a/src/router/outlet.ts +++ b/src/router/outlet.ts @@ -1,25 +1,26 @@ import { + ComponentRef, + DestroyRef, Directive, effect, inject, Type, ViewContainerRef, } from '@angular/core'; - -import { AnyRoute } from '@tanstack/router-core'; -import { injectRouteContext, injectRouter } from './router'; +import { AnyRoute, RouterState } from '@tanstack/router-core'; import { context } from './context'; +import { injectRouteContext, injectRouter } from './router'; -@Directive({ - selector: 'outlet', -}) +@Directive({ selector: 'outlet', exportAs: 'outlet' }) export class Outlet { - private cmp!: Type; private context? = injectRouteContext(); private router = injectRouter(); private vcr = inject(ViewContainerRef); + private cmp: Type | null = null; + private cmpRef: ComponentRef | null = null; + constructor() { effect(() => { const routerState = this.router.routerState(); @@ -47,19 +48,25 @@ export class Outlet { if (this.cmp !== currentCmp) { this.vcr.clear(); - this.vcr.createComponent(currentCmp, { + this.cmpRef = this.vcr.createComponent(currentCmp, { injector, environmentInjector, }); this.cmp = currentCmp; + } else { + this.cmpRef?.changeDetectorRef.markForCheck(); } }); + + inject(DestroyRef).onDestroy(() => { + this.vcr.clear(); + this.cmp = null; + this.cmpRef = null; + }); } - getMatch(matches: any[]): any { + getMatch(matches: RouterState['matches']) { const idx = matches.findIndex((match) => match.id === this.context?.id); - const matchesToRender = matches[idx + 1]; - - return matchesToRender; + return matches[idx + 1]; } } diff --git a/src/router/route.ts b/src/router/route.ts index 9325971..79f4796 100644 --- a/src/router/route.ts +++ b/src/router/route.ts @@ -189,17 +189,15 @@ export function createRoute< TChildren > { if (options.loader) { - const originalLoader = options.loader; - options.loader = (...args: Parameters) => { - const { context, route } = args[0]; - const routeInjector = ( - context as RouterContext - ).getRouteInjector(route.id); - return runInInjectionContext( - routeInjector, - originalLoader.bind(null, ...args) - ); - }; + options.loader = runFnInInjectionContext(options.loader); + } + + if (options.shouldReload && typeof options.shouldReload === 'function') { + options.shouldReload = runFnInInjectionContext(options.shouldReload); + } + + if (options.beforeLoad) { + options.beforeLoad = runFnInInjectionContext(options.beforeLoad); } return new Route< @@ -292,6 +290,18 @@ export function createRootRoute< unknown, unknown > { + if (options?.loader) { + options.loader = runFnInInjectionContext(options.loader); + } + + if (options?.shouldReload && typeof options.shouldReload === 'function') { + options.shouldReload = runFnInInjectionContext(options.shouldReload); + } + + if (options?.beforeLoad) { + options.beforeLoad = runFnInInjectionContext(options.beforeLoad); + } + return new RootRoute< TSearchValidator, TRouterContext, @@ -358,3 +368,12 @@ export class NotFoundRoute< }); } } + +function runFnInInjectionContext any>(fn: TFn) { + const originalFn = fn; + return (...args: Parameters) => { + const { context, route } = args[0]; + const routeInjector = context.getRouteInjector(route.id); + return runInInjectionContext(routeInjector, originalFn.bind(null, ...args)); + }; +}