You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Not possible to navigate away from the named outlet #51373
Which @angular/* package(s) are the source of the bug?
router
Is this a regression?
Yes
Description
I have an app with complex routing structure where named outlets are used extensively. Accordingly to the official documentation I should be able to navigate away from the named outlet by simply null-ying it's navigation path:
closePopup() {
// Providing a `null` value to the named outlet
// clears the contents of the named outlet
this.router.navigate([{ outlets: { popup: null }}]);
}
Unfortunately this is not possible at all unless I use a parent route of a route where named outlet is located.
Maybe the problem is that a named outlet is used not on the app's root level but inside a lazy-loaded module? Or that the parent of the named outlet has an empty path?
If this is expected behavior I would be thankful for an explanation why the code from the official documentations doesn't work in my case and what is going on underneath. Why a parent route is required in this case?
Please provide a link to a minimal reproduction of the bug
This is working as expected. Commands are applied relative to a particular point in the activated route tree. The documentation had been updated in #40272 to clarify the nature of the example and why it works but unfortunately it looks like this information got lost along the way (it appears at the time, I closed the wrong PR that had duplicated some other work). I will send a new PR to update the documentation again.
Commands are applied relative to a particular point in the activated route tree
This I understand and made necessary changes in the code but your answer doesn't answer the main question: why is parent of the route required?
A named outlet is a child of ShellComponent's route (see a screnshot in my previous comment), I use ShellComponent's route to navigate to the named outlet BUT a parent of the ShellComponent's route is required to close it. If close2() would have worked, it would be completely logical behavior but it didn't.
UPDATE:
From the PR you have references earlier:
We specify the parent of the `ActivatedRoute` as the `relativeTo` option because we want to remove `'popup'` from the segment which holds its reference.
In the official example private route: ActivatedRoute works fine but not in my case (see implementation of close1(), even opening a named outlet won't always work with private route: ActivatedRoute). That is why a ShellComponent's activated route is used everywhere, since it owns a named outlet. BUT suddenly I have to use shellComponentActivatedRoute.parent to navigate away from the outlet O_o and new version of the documentation doesn't explain why. IMHO this is more important for better understanding than a short example.
@pkozlowski-opensource could you please reopen this issue? The documentation is broken for sure, so above mentioned fix is required. But in my opinion even the new version of the docs doesn't explain why my example doesn't work as expected.
@atscott@pkozlowski-opensource I managed to create a better example that shows the root problem. Please check my repository, branches v2 and v3.
I thought that Angular Router operates like a browser rendering engine with shadow DOM but I was wrong in this particular case. My initial implementation had URLs in the following format: http://localhost:59312/admin/finances/(1/details/i-want-more//sidebar:notifications). It looks okay but in fact you don't see one additional level here:
That is why your corrected example won't work (see v2 branch). The reason is that a named outlet is attached to the first route in the hierarchy that has a name (in my case outside my module1! sick!) and not to its direct parent like expected! Because of this I have to use ShellComponent's parent route (which as I wrote previously is completely against any logic).
If I artificially rename the module1's root route (see v3 branch) then suddenly your example starts working, as well as ShellComponent's route! Magic :))
IMHO, current router's implementation is broken because it requires knowledge about complete routing structure. Of course inside a single module this is still manageable, even though every update of your routes can potentially break navigation.
But what if a parent module changes it's routes? In the branch v4 you can find a new commit that I personally would consider as a safe change since it doesn't change visible URL structure:
But as appears it breaks navigation in named outlets (again!), since now one would require to use .parent.parent to exit the outlet.
This I understand and made necessary changes in the code but your answer doesn't answer the main question: why is parent of the route required?
The parent route is the one that would have the reference to the named outlet. From itself, you're already inside the tree applying commands to the named outlet itself.
a named outlet is attached to the first route in the hierarchy that has a name
Yes, there's nowhere else for it to go. Empty paths don't who up in the URL but the named outlet has to go somewhere.
IMHO, current router's implementation is broken because it requires knowledge about complete routing structure. Of course inside a single module this is still manageable, even though every update of your routes can potentially break navigation.
I appreciate your opinion but it's not broken, though it can be difficult to use in some scenarios. The PR updates the docs to mention that you can close outlets from anywhere by directly modifying the UrlSegmentGroup for cases like this (https://stackblitz.com/edit/close-outlet-from-anywhere).
The parent route is the one that would have the reference to the named outlet.
Okay but this still doesn't answer the question: why a parent depends on the name of the route?
Also in this case we are talking about two types of parenting: one is the static direct parent defined in routes config (in my case this is a ShellComponent route) and second is a "runtime parent" that can change depending on your routes configuration (in the branch v2 the parent route is outside of my module, in v3 branch it is like expected). Sometimes they match, sometimes they don't and there is no explanation why.
Yes, there's nowhere else for it to go. Empty paths don't who up in the URL but the named outlet has to go somewhere.
It is like you said when we are talking solely about the visual/text representation of the routes tree. Visual representation doesn't give you enough information about the exact routes configuration BUT a router works with virtual tree (that is a result of parsing of a URL matched with the routes config) and not just an array of strings, so a named outlet can be attached to an empty route.
For example, here is a URL http://localhost:4200/admin/finances/(1/details/i-want-more//sidebar:notifications). If a router's logic worked like you said I wouldn't see a node with an empty path because visually it is not represented in the URL. But it is there, it is in the the list ActivatedRoute.pathFromRoot and has all the attributes like routeConfig as expected:
So, a router definitely knows about the correct full path to the outlet but by some reason doesn't attach this outlet to the correct parent.
The PR updates the docs to mention that you can close outlets from anywhere by directly modifying the UrlSegmentGroup for cases like this
This is a workaround. Yes, it works but an expected behavior (opening and closing of an outlet with the same ActivatedRoute) is still broken.
visual representation doesn't give you enough information about the exact routes configuration BUT a router works with virtual tree (that is a result of parsing of a URL matched with the routes config) and not just an array of strings, so a named outlet can be attached to an empty route.
Indeed, this is/was a big problem. The router has to deal with both representations because the virtual representation has to be able to be represented as a concrete URL.
If you update your example to version 16, then each of your last 3 buttons in the v2 example actually work to close the outlet (Close (using ActivatedRoute.Parent), Close (using Shell's ActivatedRoute), and Close (using Shell's ActivatedRoute parent)). Pre-v16 the router would apply the commands against the router's URL tree, which was only the real representation of the URL as it appears in the browser bar. In v16 and beyond, the commands apply against the ActivatedRoute instead.
For reference, the PR that made this change was #48508 and the related bugs were #42191, #38276, #22763,
and #48472. These things are really difficult to change and the solution to this problem began in 2021 and involved several breaking changes in major versions along the way.
These things are really difficult to change and the solution to this problem began in 2021 and involved several breaking changes in major versions along the way.
I can imagine this is not an easy 15 minutes walk in the park (( And as a person that [unfortunately] remembers a number of home-grown jQuery-based SPA-like frameworks that preceded Angular, I really appreciate all your efforts, guys!
If you update your example to version 16, then each of your last 3 buttons in the v2 example actually work to close the outlet
I've upgraded my sample to v16.2 and can confirm that it works almost as expected. If it worked like this in pre-v16 I certainly wouldn't have noticed anything wrong.
Why I say "almost"? Because as you wrote last 3 buttons in the v2 example actually work but it had to be just two buttons: one that uses activatedRoute.parent and another one that uses shellActivatedRoute.
Latest router implementation still attaches a named outlet to the (in my opinion) wrong node but now it hides this issue, so it looks fine. Here is a screenshot of the routes:
Currently I can't imagine any real world use-case where one could potentially notice that the outlet is not on its place, so case is closed :))
I sincerely tank you for your time and efforts!
angular-automatic-lock-bot commented on Sep 17, 2023
Activity
atscott commentedon Aug 15, 2023
This is working as expected. Commands are applied relative to a particular point in the activated route tree. The documentation had been updated in #40272 to clarify the nature of the example and why it works but unfortunately it looks like this information got lost along the way (it appears at the time, I closed the wrong PR that had duplicated some other work). I will send a new PR to update the documentation again.
docs(router): Clarify how to clear secondary routes
docs(router): Clarify how to clear secondary routes (#51376)
usarskyy commentedon Aug 16, 2023
@atscott Thx for a quick answer!
Commands are applied relative to a particular point in the activated route tree
This I understand and made necessary changes in the code but your answer doesn't answer the main question: why is parent of the route required?

A named outlet is a child of ShellComponent's route (see a screnshot in my previous comment), I use ShellComponent's route to navigate to the named outlet BUT a parent of the ShellComponent's route is required to close it. If
close2()would have worked, it would be completely logical behavior but it didn't.UPDATE:
From the PR you have references earlier:
In the official example
private route: ActivatedRouteworks fine but not in my case (see implementation ofclose1(), even opening a named outlet won't always work withprivate route: ActivatedRoute). That is why a ShellComponent's activated route is used everywhere, since it owns a named outlet. BUT suddenly I have to useshellComponentActivatedRoute.parentto navigate away from the outlet O_o and new version of the documentation doesn't explain why. IMHO this is more important for better understanding than a short example.usarskyy commentedon Aug 16, 2023
@pkozlowski-opensource could you please reopen this issue? The documentation is broken for sure, so above mentioned fix is required. But in my opinion even the new version of the docs doesn't explain why my example doesn't work as expected.
usarskyy commentedon Aug 16, 2023
@atscott @pkozlowski-opensource I managed to create a better example that shows the root problem. Please check my repository, branches v2 and v3.
I thought that Angular Router operates like a browser rendering engine with shadow DOM but I was wrong in this particular case. My initial implementation had URLs in the following format:

http://localhost:59312/admin/finances/(1/details/i-want-more//sidebar:notifications). It looks okay but in fact you don't see one additional level here:That is why your corrected example won't work (see v2 branch). The reason is that a named outlet is attached to the first route in the hierarchy that has a name (in my case outside my module1! sick!) and not to its direct parent like expected! Because of this I have to use ShellComponent's parent route (which as I wrote previously is completely against any logic).
If I artificially rename the module1's root route (see v3 branch) then suddenly your example starts working, as well as ShellComponent's route! Magic :))

IMHO, current router's implementation is broken because it requires knowledge about complete routing structure. Of course inside a single module this is still manageable, even though every update of your routes can potentially break navigation.
But what if a parent module changes it's routes? In the branch

v4you can find a new commit that I personally would consider as a safe change since it doesn't change visible URL structure:But as appears it breaks navigation in named outlets (again!), since now one would require to use
.parent.parentto exit the outlet.atscott commentedon Aug 16, 2023
The parent route is the one that would have the reference to the named outlet. From itself, you're already inside the tree applying commands to the named outlet itself.
Yes, there's nowhere else for it to go. Empty paths don't who up in the URL but the named outlet has to go somewhere.
I appreciate your opinion but it's not broken, though it can be difficult to use in some scenarios. The PR updates the docs to mention that you can close outlets from anywhere by directly modifying the
UrlSegmentGroupfor cases like this (https://stackblitz.com/edit/close-outlet-from-anywhere).usarskyy commentedon Aug 16, 2023
@atscott
Okay but this still doesn't answer the question: why a parent depends on the name of the route?
Also in this case we are talking about two types of parenting: one is the static direct parent defined in routes config (in my case this is a ShellComponent route) and second is a "runtime parent" that can change depending on your routes configuration (in the branch v2 the parent route is outside of my module, in v3 branch it is like expected). Sometimes they match, sometimes they don't and there is no explanation why.
It is like you said when we are talking solely about the visual/text representation of the routes tree. Visual representation doesn't give you enough information about the exact routes configuration BUT a router works with virtual tree (that is a result of parsing of a URL matched with the routes config) and not just an array of strings, so a named outlet can be attached to an empty route.
For example, here is a URL

http://localhost:4200/admin/finances/(1/details/i-want-more//sidebar:notifications). If a router's logic worked like you said I wouldn't see a node with an empty path because visually it is not represented in the URL. But it is there, it is in the the listActivatedRoute.pathFromRootand has all the attributes likerouteConfigas expected:So, a router definitely knows about the correct full path to the outlet but by some reason doesn't attach this outlet to the correct parent.
This is a workaround. Yes, it works but an expected behavior (opening and closing of an outlet with the same
ActivatedRoute) is still broken.atscott commentedon Aug 16, 2023
Indeed, this is/was a big problem. The router has to deal with both representations because the virtual representation has to be able to be represented as a concrete URL.
If you update your example to version 16, then each of your last 3 buttons in the v2 example actually work to close the outlet (Close (using ActivatedRoute.Parent), Close (using Shell's ActivatedRoute), and Close (using Shell's ActivatedRoute parent)). Pre-v16 the router would apply the commands against the router's URL tree, which was only the real representation of the URL as it appears in the browser bar. In v16 and beyond, the commands apply against the
ActivatedRouteinstead.For reference, the PR that made this change was #48508 and the related bugs were #42191, #38276, #22763,
and #48472. These things are really difficult to change and the solution to this problem began in 2021 and involved several breaking changes in major versions along the way.
usarskyy commentedon Aug 17, 2023
@atscott
I can imagine this is not an easy 15 minutes walk in the park (( And as a person that [unfortunately] remembers a number of home-grown jQuery-based SPA-like frameworks that preceded Angular, I really appreciate all your efforts, guys!
I've upgraded my sample to v16.2 and can confirm that it works almost as expected. If it worked like this in pre-v16 I certainly wouldn't have noticed anything wrong.

Why I say "almost"? Because as you wrote
last 3 buttons in the v2 example actually workbut it had to be just two buttons: one that usesactivatedRoute.parentand another one that usesshellActivatedRoute.Latest router implementation still attaches a named outlet to the (in my opinion) wrong node but now it hides this issue, so it looks fine. Here is a screenshot of the routes:
Currently I can't imagine any real world use-case where one could potentially notice that the outlet is not on its place, so case is closed :))
I sincerely tank you for your time and efforts!
angular-automatic-lock-bot commentedon Sep 17, 2023
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.
docs(router): Clarify how to clear secondary routes (angular#51376)
docs(router): Clarify how to clear secondary routes (angular#51376)