question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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:closed
  • Created 5 years ago
  • Comments:12 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
christopherthielencommented, Dec 15, 2019

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 state lazy.** is first deregistered and the newly lazy loaded states are registered.

the router that’s trying to resolve the lazy-loaded state has no clue the lazy-loaded state even exists.

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.

export const placeholder = { name: "placeholder", url: "/placeholder" };
export const placeholderChild = { name: "placeholder.child", url: "/child" };
export const placeholderNest = { name: "placeholder.child.nest", url: "/nest" };

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 and url properties, or it could copy them from the eager definitions so as not to have two sources of truth.

import * as eager from './eager.states';
export const placeholder = {
  ...eager.placeholder,
  component: Lazy1Component,
  resolve: ...
}
export const placeholderChild = {
  ...eager.placeholderChild,
  component: Lazy2Component,
  onEnter: ...
}

...

However, we would still need a hook that lazy loads the module, deregisters the partial states, and loads the lazy NgModule. Unfortunately, unlike the lazyLoad callback on a state, the loadChildren callback doesn’t provide any arguments to allow state deregistration. However, by examining the implementation of loadChildren, we can work around this deficiency by supplying our own lazyLoad function that does all the magic. This callback only needs to be on the root state of the placeholdert state tree because lazyLoad is an entering hook.

Our new state definition looks like:

export const placeholder = {
  name: "placeholder",
  url: "/placeholder",
  // This hack replaces the `loadChildren` with a `lazyLoad` block.
  // Unlike loadChildren, lazyLoad has access to the current Transition object.
  // In a future version of uirouter/angular, perhaps loadChildren would have access to the Transiton.
  lazyLoad: (transition, stateObject) => {
    const { stateRegistry } = transition.router;
    // loadNgModule is an internal detail of the `loadChildren` implementation.
    // See https://github.com/ui-router/angular/blob/master/src/statebuilders/lazyLoad.ts
    const callback = loadNgModule(() => import("./placeholder.module")
      .then(m => {
        // Deregister the eager placeholders before loading the lazy loaded NgModule.
        stateRegistry.deregister('placeholder');
        return m.PlaceholderModule;
      }));
    return callback(transition, stateObject);
  },
};

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 the lazyLoad function as well. This can cause an infinite lazy loading loop which we don’t want, so filter out the lazyLoad property from the full state.

export const placeholderRoot = { 
  // Copy the details from the eager half
  ...eager.placeholder,  
  // Add any lazy loaded stuff
  component: Lazy1Component,
  // Set layLoad to undefined so we do not infinitely loop attempting to
  // lazy load the root placeholder tree
  lazyLoad: undefined,
};

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

0reactions
christopherthielencommented, Nov 18, 2020

@cloakedninjas

There are two things being discussed in this issue:

  1. The original reporter tried to access a state via the name lazy.a but the state was actually named a. Children of future states should be prefixed by the future state name (instead of { name: 'a', parent: 'lazy' } it should be name: 'lazy.a')
  2. Sref URLs for not-yet-loaded future states are not rendered with the full URL. When the lazy module is loaded, the URLs are then updated.

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

Read more comments on GitHub >

github_iconTop Results From Across the Web

COVID-19: At least a third of the world's schoolchildren unable ...
COVID-19: At least a third of the world's schoolchildren unable to access remote learning during school closures, new report says. UNICEF's ...
Read more >
Reunification: Bringing Your Children Home From Foster Care
If the child remains in foster care for 15 out of 22 months, in most cases, the law requires the child welfare agency...
Read more >
FSSA Child Support Payment History - IN.gov
This Website allows Custodial Parents to view the last five child support payments disbursed on their case(s).
Read more >
Policy Basics: Temporary Assistance for Needy Families
While states can set their own time limit policies, they cannot provide cash assistance from federal TANF funds for longer than 60 months...
Read more >
Children's Online Privacy Protection Rule ("COPPA")
COPPA imposes certain requirements on operators of websites or online services directed to children under 13 years of age, and on operators of...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found