Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Router • Allow to have a PreloadingStrategy in lazy loaded modules #20912

Open
jeremyputeaux opened this issue Dec 9, 2017 · 10 comments
Open
Labels
area: router feature: under consideration Feature request for which voting has completed and the request is now under consideration feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature freq2: medium router: lazy loading
Milestone

Comments

@jeremyputeaux
Copy link


[ ] Regression
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support reques

Current behavior

Currently, we can only set a PreloadingStrategy from forRoot by using config like in RouterModule.forRoot(routes: Routes, config?: ExtraOptions).

Why can't this be done in forChild RouterModule.forChild(routes: Routes)?

https://angular.io/api/router/RouterModule

Expected behavior

Being able to preload a module from another lazy loaded module.

What is the motivation / use case for changing the behavior?

My registration page is lazy loaded since only members subscribing need to load it.
The page right after could be lazy loaded.

@jasonaden jasonaden added area: router feature Issue that requests a new feature freq2: medium labels Dec 11, 2017
@arlowhite
Copy link

Not sure if it should be a core feature, but you can create your own preloadingStrategy. This is how I preload certain lazy modules:

  { 
    path: 'mypath', loadChildren: './path/to/my.module#MyModule',
    data: { preload: true } 
  },
/**
 * Preload Routes with data.preload true
 * https://angular.io/guide/router#custom-preloading-strategy
 */
@Injectable()
export class SelectivePreloadingStrategy implements PreloadingStrategy {

  preloadedModules: string[] = [];

  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data['preload']) {
      // add the route path to the preloaded module array
      this.preloadedModules.push(route.path);

      return load();
    } else {
      return Observable.of(null);
    }
  }
}
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: SelectivePreloadingStrategy })
  ],

@jeremyputeaux
Copy link
Author

@arlowhite No I know about the preloadingStrategy in forRoot. My point is about having one in forChild.

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@tinchomm
Copy link

Hi Jeremy I came back to see the status of this issue. I'd really like to have this implemented. In the mid time I leave this PreloadingStrategy I used to achieve lazy loading nested modules only when I wanted.

First some context, the requirements of my app:

  • I needed to lazy load a detail module if the user successfully logs in
  • The detail module has subsections which are different modules
  • The subsection modules are also lazy loaded and reffered in detail module routing
  • Finally, not all subsection modules were available to all users, depending the permissions of the user some subsections were innaccesible so those modules shouldn't load
  • If a user logs in and load subsections 1, 2, 4 (not allowed to see 3), then logs out and another user logged in with higher permissions i.e. could see 1, 2, 3, 4; then the module 3 (still unloaded) should load.
  • There is a SomePermissionsService that has a stream permissions$ where an observer can subscribe to check each time the logged user permissions change.

I had three types of "module loading"

  1. modules that preloaded at the start (no permissions needed) with the property data: { preload: true}
  2. modules that preloaded only if user had permission, setting property data: { requestedPermission: "moderatorRole"}
  3. eager loaded modules

I could achieve exactly what I wanted with this strategy:
selective-preloading-strategy.ts

@Injectable()
export class SelectivePreloadingStrategy implements PreloadingStrategy {
  private preloadedModules: string[] = [];
  private subscriptions: { [k: string]: any } = {};

  constructor(private permissionsService: SomePermissionsService) {}

  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data["preload"]) {
      this.preloadedModules.push(route.path);
      return load();
    } else {
      if (route.data && route.data["requestedPermission"]) {
        this.subscriptions[route.path] = 
        this.permissionsService.permissions$
          .subscribe(permisos => {
            if (permisos.hasOwnProperty(route.data.requestedPermission)) {
              if (this.subscriptions[route.path]) {
                this.subscriptions[route.path].unsubscribe();
              }
              if (!this.preloadedModules.includes(route.path)) {
                this.preloadedModules.push(route.path);
                return load();
              }
            }
          });
        return this.subscriptions[route.path];
      }
      return Observable.of(null);
    }
  }
}

Then in the AppRoutingModule:

@NgModule({
  imports: [
    RouterModule.forRoot(MAIN_ROUTES, {
      preloadingStrategy: SelectivePreloadingStrategy
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

If we take a look at each part:

  private preloadedModules: string[] = [];
  private subscriptions: { [k: string]: any } = {};

  constructor(private permissionsService: SomePermissionsService) {}

preloadedModules is where we are going to track which modules have already been preloaded
subscriptions is going to be a dictionary containing as key the module (path) and the value will be a subscription that will check each time a user logs in if he/she has the permissions to load that module.
permissionsService is the service that will provide us the permissions Observable

 if (route.data && route.data["preload"]) {
      this.preloadedModules.push(route.path);
      return load();
    }

If the module is set to always be preloaded, do it

Else

      if (route.data && route.data["requestedPermission"]) {
        this.subscriptions[route.path] = 
        this.permissionsService.permissions$
          .subscribe(permissions => {
            if (permissions .hasOwnProperty(route.data.requestedPermission)) {
              if (this.subscriptions[route.path]) {
                this.subscriptions[route.path].unsubscribe();
              }
              if (!this.preloadedModules.includes(route.path)) {
                this.preloadedModules.push(route.path);
                return load();
              }
            }
          });
        return this.subscriptions[route.path];
      }

If the route has the property data["requestedPermission"] set, we create a subscription with the path in the subscriptions dictionary of permissionsService.permissions$ and return it.

What does the observer do each time permissions are emitted?
If the new permissions of the user contains the one needed to preload the module, we:

  1. Unsubscribe that path(module) subscription
  2. If check that the module wasn't preloaded before, we add it to the preloadedModules array and load it.
    (It was necessary to check the preloading because for some reason the observer for the module was firing twice before unsubscribing)

Finally if the module should not be preloaded return Observable.of(null);

It was my first project handling observers so probably there are better ways of achiving this, but this worked as a charm without performance nor memory issues.
I hope it helps.

@adamdport
Copy link

Even if you defined a different preloading strategy in a child, I found that preloading was too eager. If you preloaded your registration module, for example, then the app would also preload the page after registration—even when you never even went to to registration. Not ideal.

I ended up implementing "checkpoints" in my routes, and define which checkpoint I need to reach before I preload a module. You can read more about it here

@ghost
Copy link

ghost commented Sep 28, 2018

Does anyone know if this feature request is being worked on in anyway?

@poddarkhushbu07
Copy link

@arlowhite No I know about the preloadingStrategy in forRoot. My point is about having one in forChild.

Why you want for forChild when preloadingStratergy is applied to forRoot, indirectly applies to all forChild also ?

@kirillgroshkov
Copy link

We need it too!

In case when I start loading my app from a page that has SelectivePreloadingStrategy in forRoot - it doesn't apply same strategy for it's forChild. I test it by putting console.log in the first line of SelectivePreloadingStrategy, I see this log for forRoot modules, but not for forChild.

@wideLandscape
Copy link

In my case I have 2 main lazy loaded module that lazy loads route childrens.
For test I created a TestPreloadingStrategy like this:

@Injectable({ providedIn: 'root' })
export class TestPreloadingStrategy implements PreloadingStrategy {
	
	constructor() {
		console.log('I exist!');
	}

	preload(route: Route, load: () => Observable<any>): Observable<any> {
		console.log('pls tell me I'm called');
                load();
	}

}

RootModule.forRoute(routes, {preloadingStrategy: TestPreloadingStrategy})

It logs only 'I exist!'

I really wish that preloadingStratergy applied to forRoot, indirectly applies to all forChild also but it's not.

Angular version: 7.2.0

@chikakow
Copy link

chikakow commented Jul 1, 2020

@wideLandscape You probably need to return load();
I did exact same except that i am returning load() and I'm getting the second log 'plz tell me i'm called'

@angular-robot angular-robot bot added the feature: votes required Feature request which is currently still in the voting phase label Jun 4, 2021
@angular-robot
Copy link
Contributor

angular-robot bot commented Jun 4, 2021

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

@angular-robot angular-robot bot added the feature: under consideration Feature request for which voting has completed and the request is now under consideration label Jun 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: router feature: under consideration Feature request for which voting has completed and the request is now under consideration feature: votes required Feature request which is currently still in the voting phase feature Issue that requests a new feature freq2: medium router: lazy loading
Projects
None yet
Development

No branches or pull requests

10 participants