Skip to content

Bugged named outlet with lazy-loaded module & empty parent route path  #48472

Closed

Description

@juliancarrivick

Which @angular/* package(s) are the source of the bug?

router

Is this a regression?

Yes

Description

This is related to an issue that has been extensively discussed in the community for a long time and been attempted to be fixed multiple times with named outlets in lazy loaded routes/parent routes with an empty path:

I think this is slightly different behaviour than previously reported: when navigating to the named route outlet the first time the route activates successfully and everything seems happy. However if you do it again the router nests another named route in the URL instead of recognising it is already active. e.g. (from stackblitz) /shell/content -> /shell/(content//help:content) -> /shell/(/(content//help:content)//help:content) etc. Additionally, navigating to [routerLink]="[{outlets: {help: null}}]" doesn't clear the named router outlet.

There is a workaround if you make the parent of the named outlet have a non-empty path (this is the previously advertised workaround), but this is not unfortunately a satisfactory solution for me. I don't have time right now to bisect and determine when it regressed but will endeaver to do this in the next few days.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-dsermn?file=src/app/shell/shell.module.ts

Please provide the exception or error you saw

As described above.

Please provide the environment you discovered this bug in (run ng version)

(Locally I'm running this, but it is still present on 15.0.3 as evidenced by the stackblitz repo)

Angular CLI: 14.2.9
Node: 16.18.1
Package Manager: npm 8.19.2
OS: linux x64

Angular: 14.2.10
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router, upgrade

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1402.9
@angular-devkit/build-angular   14.2.9
@angular-devkit/core            14.2.9
@angular-devkit/schematics      14.2.9
@angular/cdk                    14.2.7
@angular/cli                    14.2.9
@angular/material               14.2.7
@schematics/angular             14.2.9
rxjs                            7.5.7
typescript                      4.7.4

Anything else?

Present on at least Angular 14 & 15.

Activity

  1. atscott commented on Dec 13, 2022

    @atscott
    Contributor

    This is actually a different issue than the ones you have linked to. The ones you linked are related to the route matching algorithm but the problem here is UrlTree creation - you can see that the routerLink href already has the problem with creating crazy URLs before you attempt navigation.

    With that, this issue is (partially) fixed by #45877. Unfortunately, there wasn't enough time for me to land it as a breaking change in v15 by swapping out the implementation of router.createUrlTree for this fixed behavior. In short, the current behavior is super round-about and is incapable of creating the correct result in many scenarios. I plan to include createUrlTreeFromSnapshot as a breaking change to router.createUrlTree in v16.

  2. juliancarrivick commented on Dec 14, 2022

    @juliancarrivick
    Author

    Great, thanks @atscott, it looks like you've been doing lots of great work in this space! 🙂 Shame it didn't make it into v15, but I'm sure I'll blink and v16 will be out!

    When you say it's been "partially" fixed, are there any particular scenarios where you know it's broken still?

  3. atscott commented on Dec 14, 2022

    @atscott
    Contributor

    By "partially" I mean that it isn't the default for router.createUrlTree (and so it's not fixed for routerLink). If it were, you wouldn't have had to file this bug in the first place :)

    edit: to answer your question directly, there are no scenarios that I know of that do not working correctly.

  4. juliancarrivick commented on Dec 14, 2022

    @juliancarrivick
    Author

    Ah, so this is just an issue with the generation of the link, not with the routing specifically.

    So, if I understand correctly, I should be able to manually craft the URL, or use the new createUrlTreeFromSnapshot function to generate the URLTree which I would be able to use directly as a workaround?

  5. juliancarrivick commented on Dec 14, 2022

    @juliancarrivick
    Author

    Yep, I confirmed this works 😄

    I had to do the navigation on the click event (and preventDefault the event), but it seems to work! I created a helper directive to encapsulate this - hope it helps someone else!

    import { Directive, HostBinding, HostListener, Input, OnDestroy } from '@angular/core';
    import { ActivatedRoute, createUrlTreeFromSnapshot, NavigationEnd, Router, UrlTree } from '@angular/router';
    import { BehaviorSubject, combineLatest, filter, map, startWith, Subscription } from 'rxjs';
    
    @Directive({
        standalone: true,
        selector: '[appHelpRouterLink]',
    }) export class HelpRouterLinkDirective implements OnDestroy {
        @Input('appHelpRouterLink') public set helpCommands(commands: unknown[] | null) {
            this.helpCommands$.next(commands);
        }
        private helpCommands$ = new BehaviorSubject<unknown[] | null>(null);
    
        @HostBinding('href') public href?: string;
        private urlTree?: UrlTree;
    
        private subscription: Subscription;
    
        public constructor(
            private router: Router,
            route: ActivatedRoute,
        ) {
            const otherNavigations = this.router.events.pipe(
                filter((e): e is NavigationEnd => e instanceof NavigationEnd),
                map(() => route.snapshot),
                startWith(route.snapshot),
                filter(r => r.outlet !== 'help'),
            );
            this.subscription = combineLatest([
                otherNavigations,
                this.helpCommands$,
            ]).subscribe(([snapshot, commands]) => {
                this.urlTree = createUrlTreeFromSnapshot(snapshot, [{ outlets: { help: commands } }]);
                this.href = this.router.serializeUrl(this.urlTree);
            });
        }
    
        public ngOnDestroy(): void {
            this.subscription.unsubscribe();
        }
    
        @HostListener('click', ['$event'])
        public navigate(event: Event) {
            if (!this.urlTree) {
                return;
            }
    
            event.preventDefault();
            this.router.navigateByUrl(this.urlTree);
        }
    }
  6. angular-automatic-lock-bot commented on Jan 14, 2023

    @angular-automatic-lock-bot

    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.

  7. locked and limited conversation to collaborators on Jan 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions