Can't access future state children
See original GitHub issue@christopherthielen We cannot target a substate defined inside a lazy loaded state which hasn’t been fetched yet.
If i have a future state with a child state :
// lazy.router.ts
const states: Ng2StateDeclaration[] = [
{
name: 'lazy',
url: '/lazy',
component: LazyComponent
},
{
name: 'a', // child state
parent: 'lazy',
url: '/a',
component: LazyAComponent
}
]
export LazyRouterStates: ModuleWithProviders = UIRouterModule.forChild({ states })
included in this module :
// lazy.module.ts
@NgModule({
imports: [SharedModule, LazyRouterStates], //shared module exports CommonModule and some other cool stuff
declarations: [LazyComponent, LazyAComponent]
})
export class LazyModule {}
lazy loaded in my main router:
// app.router.ts
const states: Ng2StateDeclaration[] = [
{
name: 'lazy.**',
url: '/lazy',
loadChildren: './lazy/lazy.module#LazyModule'
}
];
export const RootRouterStates: ModuleWithProviders = UIRouterModule.forRoot({ states, config });
referenced in the main module:
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, SharedModule, RootRouterStates],
providers: [{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }],
bootstrap: [AppComponent]
})
export class AppModule {}
I cannot reach the lazy.a
state from my main component:
<!-- app.component.html -->
<a uiSref="lazy">Lazy</a> <!-- works and creates http://some-adresse/lazy link -->
<a uiSref="lazy.a">Lazy > A</a> <!-- doesn't work and creates http://some-adresse/lazy link -->
Since lazy.a
state url is unknown when the router definition hasn’t been fetched from the server, StateService.href(...)
used in the uiSref directive update()
function can’t return the correct href because it has no means to know it.
Same thing when you manually try to reach the state with some click function calling StateService.go(...)
:
// stateService injected in the component ctor
someOnClickFn(): void {
this.stateService.go('lazy.a'); //doesn't work
}
It would be nice to pass a relative or absolute url or some kind of option to the uisref to achieve this. For example in this case something like:
<a uiSref="lazy.a" [uiOptions]="{ lazyPath: '/a' }">Lazy > a</a>
<!-- or directly the path -->
<a uiSref="/lazy/a">Lazy > a</a>
Thanks in advance.
Issue Analytics
- State:
- Created 5 years ago
- Comments:12 (3 by maintainers)
Top GitHub Comments
In the future state paradigm, a single placeholder state is loaded up front. This placeholder state has wildcards which match all child states (
name: 'lazy.**'
) and match all child URLs (url: '/lazy*'
). When a URL is activated which matches/lazy*
, or when the a uiSref is activated to a state name matchinglazy.**
, the router runs the future state’s lazy load code. Upon success, the the future statelazy.**
is first deregistered and the newly lazy loaded states are registered.As @roelofjan-elsinga mentions, the router doesn’t know the details of the lazy states until after they have actually been lazy loaded. If the goal is to have correct URLs in links for lazy loaded states, perhaps we can eagerly provide that information to the router.
A new lazy load paradigm
Instead of using the future state paradigm, the lazy module can eagerly provide partial state definitions to the router. The partial definitions should be just enough to generate proper URLs. When any of those states are activated, the module code can be lazy loaded, the partial state definitions deregistered, and the full state definitions registered.
The eagerly provided partial state definitions can be as simple as a state name and url.
This would allow the router to generate proper urls for, e.g.,
placeholder.child.nest
.When the full module is lazy loaded, it can provide full state definitions, including components and resolves. It can either duplicate the
name
andurl
properties, or it could copy them from the eager definitions so as not to have two sources of truth.However, we would still need a hook that lazy loads the module, deregisters the partial states, and loads the lazy
NgModule
. Unfortunately, unlike thelazyLoad
callback on a state, theloadChildren
callback doesn’t provide any arguments to allow state deregistration. However, by examining the implementation ofloadChildren
, we can work around this deficiency by supplying our ownlazyLoad
function that does all the magic. This callback only needs to be on the root state of the placeholdert state tree becauselazyLoad
is anentering
hook.Our new state definition looks like:
One more detail to consider… when defining the lazy half of these states, if we copy all the properties from the eager portion (
{ ...eager.placeholder, }
), we’ve now copied thelazyLoad
function as well. This can cause an infinite lazy loading loop which we don’t want, so filter out thelazyLoad
property from the full state.Example
I’ve created a proof of concept on stackblitz that demonstrates both paradigms (one future state tree and one eager/lazy placeholder tree)
https://stackblitz.com/edit/uirouter-angular-lazy-load-placeholders?file=src%2Fplaceholder%2Fplaceholder.states.eager.ts
I’d like to hear your thoughts
@cloakedninjas
There are two things being discussed in this issue:
lazy.a
but the state was actually nameda
. Children of future states should be prefixed by the future state name (instead of{ name: 'a', parent: 'lazy' }
it should bename: 'lazy.a'
)To be clear: Srefs from the root module to lazy loaded modules are supported, with the caveats above.
I commented with a workaround to address issue #2