27
loading...
This website collects cookies to deliver better user experience
UrlTree
, UrlSegmentGroup
, they will be briefly described before being applied. UrlTree
.UrlTree
, which Angular Router will further use to determine whether a configuration exists or not for that route. It can achieve that by traversing the given Routes
configuration array and the UrlTree
simultaneously. This is how the UrlTree
structure looks like:export class UrlTree {
/* ... */
constructor(
/** The root segment group of the URL tree */
public root: UrlSegmentGroup,
/** The query params of the URL */
public queryParams: Params,
/** The fragment of the URL */
public fragment: string|null) {}
}
queryParams
and fragment
. It looks like only the segments of a URL are missing. For that, there is UrlSegmentGroup
, which looks as follows:export class UrlSegmentGroup {
/* ... */
parent: UrlSegmentGroup|null = null;
constructor(
/** The URL segments of this group. See `UrlSegment` for more information */
public segments: UrlSegment[],
/** The list of children of this group */
public children: {[key: string]: UrlSegmentGroup}) {
forEach(children, (v: any, k: any) => v.parent = this);
}
}
UrlTree
, because a URL can apparently be seen as a tree of segments. Now it comes the genuine question: why would you need a tree-like structure to represent the segments of a URL? The answer is because Angular Router also supports named outlets and in fact, every property from the children
object form above represents a named outlet. It should also be mentioned that when no outlet is specified, the primary outlet is used by default.UrlSegment
is used to represent a URL segment and for each segment it keeps track of the name and the segment parameters.'foo/123/(a//named:b)'
(where named
refers to a named outlet called named
), its equivalent UrlTree
will be:{
segments: [], // The root UrlSegmentGroup never has any segments
children: {
primary: {
segments: [{ path: 'foo', parameters: {} }, { path: '123', parameters: {} }],
children: {
primary: { segments: [{ path: 'a', parameters: {} }], children: {} },
named: { segments: [{ path: 'b', parameters: {} }], children: {} },
},
},
},
}
Routes
configuration array. A configuration that would match the given URL would be this one:{
// app-routing.module.ts
{
path: 'foo/:id',
loadChildren: () => import('./foo/foo.module').then(m => m.FooModule)
},
// foo.module.ts
{
path: 'a',
component: AComponent,
},
{
path: 'b',
component: BComponent,
outlet: 'named',
},
}
UrlTree
, it's time to see the problem we are trying to solve.UrlTree
, I'd recommend having a look at Angular Router: Getting to know UrlTree, ActivatedRouteSnapshot and ActivatedRoute.const routes: Routes = [
{
path: '',
component: FooContainer1,
children: [
{
path: '',
component: FooContainer2,
children: [
{
path: ':id',
component: FooComponent1,
outlet: 'test'
},
{
path: '',
pathMatch: 'full',
component: DummyComponent1
}
]
}
]
}
];
FooComponent1
component?<button [routerLink]="['/', { outlets: { test: [123] } }]"><!-- ... --></button>
UrlTree
of the above is:{
fragment: undefined
queryParams: {}
root: {
children:
test: {
children: {}
segments: [{ path: '123' }]
}
segments: []
}
}
Routes
configuration and a UrlSegmentGroup
./
) in the Route
's path
property does not have to be equal to the number of UrlSegmentGroup.segments
. In order for a Route
to be matched, the numbers of segments in the path
property must be less than or equal to the length of UrlSegmentGroup.segments
. If the previous condition is met, the some of the UrlSegmentGroup.segments
segments are said to be consumed.if (route.path === '') {
if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
throw new NoMatch();
}
return {consumedSegments: [], lastChild: 0, parameters: {}};
}
UrlSegmentGroup.segments
are consumed and UrlSegmentGroup.children
is not empty:// The `UrlTree`
{
segments: [], // The root UrlSegmentGroup never has any segments
children: {
primary: {
segments: [{ path: 'foo', parameters: {} }, { path: '123', parameters: {} }],
children: {
primary: { segments: [{ path: 'a', parameters: {} }], children: {} },
named: { segments: [{ path: 'b', parameters: {} }], children: {} },
},
},
},
}
// The configuration
{
// app-routing.module.ts
{
path: 'foo/:id',
loadChildren: () => import('./foo/foo.module').then(m => m.FooModule)
},
// foo.module.ts
{
path: 'a',
component: AComponent,
},
{
path: 'b',
component: BComponent,
outlet: 'named',
},
}
UrlSegmentGroup.children
's values are named outlets and their segments.UrlSegmentGroup.segments
are consumed and UrlSegmentGroup.children
is empty:
const routes: Routes = [
{
path: 'foo/bar'
}
];
foo/bar
.UrlTree
for foo/bar
looks like:{
fragment: null,
queryParams: {},
root: {
children: {
primary: {
// It is empty
children: {},
// Both will be *consumed*
segments: [{ path: 'foo', parameters: {} }, { path: 'bar', parameters: {} }]
}
},
segments: [],
}
}
UrlSegmentGroup.segments
have been consumed:UrlSegmentGroup.segments
are consumed. In this case, if the current Route
object has either a children
property or loadChildren
, it will traverse the array found in one of these properties.Routes
configuration array, it will not take into account the current outlet name. Recall that an outlet's name is a property in the UrlSegmentGroup.children
object.const routes: Routes = [
{
path: '',
component: FooContainer1,
children: [
{
path: '',
component: FooContainer2,
children: [
{
path: ':id',
component: FooComponent1,
outlet: 'test'
},
{
path: '',
pathMatch: 'full',
component: DummyComponent1
}
]
}
]
}
];
<button [routerLink]="['/', { outlets: { test: [123] } }]"><!-- ... --></button>
path
is ''
, the UrlSegmentGroup.segments
won't be consumed(here's why). The way this is handled in earlier versions is to always use the primary outlet name, although the current outlet name might be different. Since the UrlTree
generated by the above RouterLink
looks like this:{
fragment: undefined
queryParams: {}
root: {
children:
// No `primary` outlet here, only `test`.
test: {
children: {}
segments: [{ path: '123' }]
}
segments: []
}
}
/* ... */
// `childConfig` in this case refers to the content of `children` property.
const matchedOnOutlet = getOutlet(route) === outlet;
const expanded$ = this.expandSegment(
childModule, segmentGroup, childConfig, slicedSegments,
matchedOnOutlet ? PRIMARY_OUTLET : outlet, true);
primary
and since all the paths until FooComponent1
are ''
, the first children
array will be traversed(denoted by(1)
), then the second children
array(denoted by (2)
) and there it will finally find the match.