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

Fix module loading to ensure each module is loaded only once #40389

Closed
wants to merge 2 commits into from

Conversation

pgammans
Copy link
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

This PR addresses the issues found in the issues #22842, PR #26557, PR #36760, PR #26557

It also replaces the PR for #26557 which has a number of issues and is an

By forcing the subscription to close if a module has a lazy sub-module then the these are passed to the PreloadingStrategy class as the observable has already terminated.
It doesn't actually prevent the load as the fetch may overlap with the preload fetch. Ie both the pre-loader and navigation can start a load both will finish.
Issue Number: #26557, #22842

What is the new behavior?

By Prevent loading a route module twice, we prevent the sending of multiple RouteConfigLoadEnd events, and the duplicate console log as show in the stackbiz example on #26557

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

This is an updated version of my originally and closed PR #36760 (due to my failure to update) but addressing @atscott comments.

Copy link
Contributor

@atscott atscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pgammans - Thanks for resubmitting this PR with the updates! A few small nits in the test and one question about finding a strategy to avoid using share but otherwise LGTM

packages/router/src/router_config_loader.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
@atscott atscott added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews target: patch This PR is targeted for the next patch release labels Jan 11, 2021
@pgammans pgammans force-pushed the fix-route-strategy branch 2 times, most recently from ab94772 to 38081cf Compare January 13, 2021 18:27
@pgammans pgammans requested a review from atscott January 13, 2021 21:50
@atscott atscott added the action: presubmit The PR is in need of a google3 presubmit label Jan 13, 2021
@atscott
Copy link
Contributor

atscott commented Jan 14, 2021

@pgammans one more thing - could you squash all of your commits into one?

@atscott
Copy link
Contributor

atscott commented Jan 15, 2021

global presubmit

@atscott atscott added action: presubmit The PR is in need of a google3 presubmit and removed action: presubmit The PR is in need of a google3 presubmit labels Jan 15, 2021
@atscott atscott removed the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Jan 19, 2021
@atscott
Copy link
Contributor

atscott commented Jan 26, 2021

Hi @pgammans - sorry for the slow response. After running internal tests, the approach we made to avoid introducing the share operator caused failures. Using the share like was there initially does work though.

I don't know if we can justify the additional payload size increase to address this edge case where a preloader and a navigation duplicate the loading of a lazy route. That would increase the payload for everyone and only prevent an extra load of a lazy module in a rare scenario so I think the cost-benefit doesn't quite work out in our favor.

What we can certainly do now though is keep the check that you added in preloadConfig to partially address the issue.

@pgammans
Copy link
Contributor Author

@atscott can i ask what the failure was and to see if i can develop a third solution that addresses all the issues.

Obviously share does something that addresses this issue it may be possible to implement a fix for this without all the overheads that the generic share function will need to address and

load(parentInjector: Injector, route: Route): Observable<LoadedRouterConfig> {
   if (route._loader$) {
     return route._loader$;
   }

   if (this.onLoadStartListener) {
     this.onLoadStartListener(route);
   }
   const moduleFactory$ = this.loadModuleFactory(route.loadChildren!);
   route._loader$ =  new ConnectableObservable(
     moduleFactory$.pipe(
       map((factory: NgModuleFactory<any>) => {
         if (this.onLoadEndListener) {
           this.onLoadEndListener(route);
         }
         const module = factory.create(parentInjector);
         // When loading a module that doesn't provide `RouterModule.forChild()` preloader will get
         // stuck in an infinite loop. The child module's Injector will look to its parent
         // `Injector` when it doesn't find any ROUTES so it will return routes for it's parent
         // module instead.or
         return new LoadedRouterConfig(
             flatten(
                 module.injector.get(ROUTES, undefined, InjectFlags.Self | InjectFlags.Optional))
                 .map(standardizeConfig),
             module);
       }),
       catchError((err) => {
         route._loader$ = undefined;
         throw err;
       }),
     ),
     () => new Subject<LoadedRouterConfig>()
   ).pipe(refCount())
   return route._loader$;

Even this may be smaller as remove some of share features.
How can i test the size change?

@atscott
Copy link
Contributor

atscott commented Jan 28, 2021

@pgammans - I haven't been able to debug the exact issue internally so I unfortunately can't provide much more information now. It's a few of the application's webdriver integration test that are failing and I'll have to figure out how to spin up the app locally to debug it.
The snippet with ConnectableObservable and refCount also passes the tests but I'm still concerned about the tradeoff in bundle size vs impact. Running the bundle size check locally doesn't seem to work (on our plate to investigate soon) but if you make the change to your PR, the CI will be able to run the test and tell you the new bundle size.

@pgammans pgammans force-pushed the fix-route-strategy branch 2 times, most recently from ce0954b to 6ac2011 Compare January 28, 2021 19:12
@pgammans
Copy link
Contributor Author

pgammans commented Feb 1, 2021

@atscott
I assume this is OK, CI says no size change. I assume this is as ConnectableObservable is already allocated in the routers golden file.

// Use custom ConnectableObservable as share increasing the bundle size too much
route._loader$ =
new ConnectableObservable(
moduleFactory$.pipe(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: What do you think of extracting this first argument of the ConnectableObservable to a variable so it's easier to see the two arguments?

Copy link
Contributor Author

@pgammans pgammans Feb 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've push a change for this and rebased to master.
I'm not 100% sure i agree as i think it breaks the link that the effective 'share' is part of the loader, but i can see an argument for separation of the loader from the share.

@atscott
Copy link
Contributor

atscott commented Feb 1, 2021

@pgammans - Yep, I believe you are correct. It looks like the goldens also already have the refcount operator. I want to run some final tests on this but at the moment it's looking good. Thanks for the work on this!

Copy link
Member

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed-for: size-tracking

packages/router/test/apply_redirects.spec.ts Outdated Show resolved Hide resolved
@petebacondarwin petebacondarwin added the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Feb 11, 2021
Fix router to ensure that a route module is only loaded once especially
in relation to the use of preload strategies with delayed or partial
loading.

Add test to check the interaction of PreloadingStrategy and normal
router navigation under differing scenarios.
Checking:
 * Prevention of duplicate loading of modules.
   related to angular#26557
 * Prevention of duplicate RouteConfigLoad(Start|End) events
   related to angular#22842
 * Ensuring preload strategy remains active for submodules if needed
   The selected preload strategy should still decide when to load submodules
 * Possibility of memory leak with unfinished preload subscription
   related to angular#26557
 * Ensure that the stored loader promise is cleared so that subsequent
   load will try the fetch again.
 * Add error handle error from loadChildren
 * Ensure we handle error from with NgModule create

Fixes angular#26557 angular#22842 angular#26557
@pgammans
Copy link
Contributor Author

@petebacondarwin I have applied your suggested commit for the spelling mistake, rebased onto master. I have also squashed all the commits as I assume this would be wanted.

@pgammans
Copy link
Contributor Author

@atscott the size of aio-local-viewengine has also increased do you want a commit to increase ti size too?

expected 432078, actual: 432647 so an increase of 569 bytes

@atscott atscott removed the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Feb 11, 2021
josephperrott pushed a commit that referenced this pull request Feb 11, 2021
#40389)

Fix router to ensure that a route module is only loaded once especially
in relation to the use of preload strategies with delayed or partial
loading.

Add test to check the interaction of PreloadingStrategy and normal
router navigation under differing scenarios.
Checking:
 * Prevention of duplicate loading of modules.
   related to #26557
 * Prevention of duplicate RouteConfigLoad(Start|End) events
   related to #22842
 * Ensuring preload strategy remains active for submodules if needed
   The selected preload strategy should still decide when to load submodules
 * Possibility of memory leak with unfinished preload subscription
   related to #26557
 * Ensure that the stored loader promise is cleared so that subsequent
   load will try the fetch again.
 * Add error handle error from loadChildren
 * Ensure we handle error from with NgModule create

Fixes #26557 #22842 #26557

PR Close #40389
@pgammans pgammans deleted the fix-route-strategy branch February 11, 2021 17:34
josephperrott added a commit to josephperrott/angular that referenced this pull request Feb 11, 2021
josephperrott added a commit that referenced this pull request Feb 11, 2021
josephperrott added a commit that referenced this pull request Feb 11, 2021
@atscott
Copy link
Contributor

atscott commented Feb 11, 2021

FYI - this fix was reverted as there were internal failures during the sync that need further investigation.

@atscott
Copy link
Contributor

atscott commented Feb 11, 2021

Okay, so the issue is that by de-duplicating the module loading and using the same router config objects, we change how routes are re-used. That is, an ActivatedRoute/component that was not previously re-used will now be re-used because the route objects have the same reference.
This is actually the correct behavior, but it's something we'll still have to address. Devs may need to update their tests if they accidentally rely on the current broken behavior. I'm still investigating how widespread this problem is.

@atscott
Copy link
Contributor

atscott commented Feb 12, 2021

Update: There was only one test failure (the one from yesterday) in the global test results. That indicates the issue isn't widespread enough to require further action other than a fix to this one test. After that is done, we can resubmit this PR.

@pgammans pgammans restored the fix-route-strategy branch February 12, 2021 19:24
@atscott atscott reopened this Feb 12, 2021
@atscott
Copy link
Contributor

atscott commented Feb 16, 2021

global presubmit

@atscott atscott added the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Feb 16, 2021
@atscott
Copy link
Contributor

atscott commented Feb 16, 2021

caretaker note: Since this PR previously had to be rolled back, I think it'd be a good idea to merge and sync on its own.

@atscott atscott linked an issue Feb 16, 2021 that may be closed by this pull request
josephperrott pushed a commit that referenced this pull request Feb 16, 2021
#40389)

Fix router to ensure that a route module is only loaded once especially
in relation to the use of preload strategies with delayed or partial
loading.

Add test to check the interaction of PreloadingStrategy and normal
router navigation under differing scenarios.
Checking:
 * Prevention of duplicate loading of modules.
   related to #26557
 * Prevention of duplicate RouteConfigLoad(Start|End) events
   related to #22842
 * Ensuring preload strategy remains active for submodules if needed
   The selected preload strategy should still decide when to load submodules
 * Possibility of memory leak with unfinished preload subscription
   related to #26557
 * Ensure that the stored loader promise is cleared so that subsequent
   load will try the fetch again.
 * Add error handle error from loadChildren
 * Ensure we handle error from with NgModule create

Fixes #26557 #22842 #26557

PR Close #40389
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 19, 2021
@pgammans pgammans deleted the fix-route-strategy branch April 19, 2021 21:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: router cla: yes merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RouteConfigLoadEnd event emitted multiple times
4 participants