angular - 为什么RouterLink将输入添加到括号中当前URL的末尾
问题描述
假设我的网址是:http://localhost:4200/user_id/home。这是我的按钮代码:
<ion-button [routerLink]="['some_user_id', 'payments']" routerLinkActive="selected">
<ion-label class="label">Payments</ion-label>
</ion-button>
因为我Error: Cannot match any routes.
开始调查问题,所以我发现 routerLink 正在生成这样的 DOM 元素:
<a href="user_id/home/(some_user_id/payments)" class="button-native" part="native">
当(在同一个组件中)我使用路由器进行导航时,例如:
this.router.navigate('some_user_id', 'payments'])
一切正常。
生成的 href 不仅仅是问题是什么<a href="some_user_id/payments" class="button-native" part="native"> as allways
?
解决方案
这是因为它routerLink
是一个指令,它在幕后做了一些其他的事情。
让我们看看当您单击具有指令的元素时会发生什么:RouterLink
@Directive({selector: ':not(a):not(area)[routerLink]'})
export class RouterLink {
/* ... */
@HostListener('click')
onClick(): boolean {
const extras = {
skipLocationChange: attrBoolValue(this.skipLocationChange),
replaceUrl: attrBoolValue(this.replaceUrl),
state: this.state,
};
this.router.navigateByUrl(this.urlTree, extras);
return true;
}
get urlTree(): UrlTree {
return this.router.createUrlTree(this.commands, {
relativeTo: this.route, // !
queryParams: this.queryParams,
fragment: this.fragment,
preserveQueryParams: attrBoolValue(this.preserve),
queryParamsHandling: this.queryParamsHandling,
preserveFragment: attrBoolValue(this.preserveFragment),
});
}
/* ... */
}
注意relativeTo: this.route
,其中this.route
指向当前ActivatedRoute
(例如与 相关联的那个/home
)。
所做的是Router.createUrlTree
将一组应用commands
到当前的 URL 树,这将产生一个新的 URL 树。在你的情况下,commands
是['some_user_id', 'payments']
.
createUrlTree(commands: any[], navigationExtras: NavigationExtras = {}): UrlTree {
const {
relativeTo,
queryParams,
fragment,
preserveQueryParams,
queryParamsHandling,
preserveFragment
} = navigationExtras;
/* .... */
const a = relativeTo || this.routerState.root;
const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
let q: Params|null = null;
/* ... resolving query params based on the `queryParamsHandling` strategy */
return createUrlTree(a, this.currentUrlTree, commands, q!, f!);
}
createUrlTree
是魔法发生的地方:
export function createUrlTree(
route: ActivatedRoute, urlTree: UrlTree, commands: any[], queryParams: Params,
fragment: string): UrlTree {
// `route` - that one which corresponds to `/home`
// `commends` - `['some_user_id', 'payments']`
// `urlTree` - a tree of UrlSegmentGroups, we'll have a closer look a bit later
if (commands.length === 0) { /* Not our case */ }
/*
a command might also be one of these objects:
* { outlets: { outletName: path } }
* { k1: v1, k2: v2 } - segment parameters
* { segmentPath: path }
but in this case, it will simply be a Navigation object {
isAbsolute: false,
numberOfDoubleDots: 0,
commands: ['some_user_id', 'payments']
}
*/
const nav = computeNavigation(commands);
if (nav.toRoot()) {
/* Not our case; */
/* It would've been if: this.isAbsolute && this.commands.length === 1 && this.commands[0] == '/' */
}
/*
We'd get a new `Position` object: `return new Position(g, false, ci - dd);`
where `dd` - number of double dots = 0 and `ci` - current index = 1
why is it 1? - https://github.com/angular/angular/blob/master/packages/router/src/create_url_tree.ts#L160
*/
const startingPosition = findStartingPosition(nav, urlTree, route);
const segmentGroup = startingPosition.processChildren ?
updateSegmentGroupChildren(
startingPosition.segmentGroup, startingPosition.index, nav.commands) :
updateSegmentGroup(startingPosition.segmentGroup, startingPosition.index, nav.commands);
return tree(startingPosition.segmentGroup, segmentGroup, urlTree, queryParams, fragment);
}
segmentGroup
将是 的结果updateSegmentGroup
。它最终会达到createNewSegmentGroup
:
function createNewSegmentGroup(
segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup {
// Everything before the `startIndex`
const paths = segmentGroup.segments.slice(0, startIndex);
let i = 0;
while (i < commands.length) {
if (typeof commands[i] === 'object' && commands[i].outlets !== undefined) {
/* Not our case */
}
// if we start with an object literal, we need to reuse the path part from the segment
// That's why the `modifier` is 1 if there are no parameters: https://github.com/angular/angular/blob/master/packages/router/src/create_url_tree.ts#L160
if (i === 0 && isMatrixParams(commands[0])) {
const p = segmentGroup.segments[startIndex];
paths.push(new UrlSegment(p.path, commands[0]));
i++;
continue;
}
const curr = getPath(commands[i]);
const next = (i < commands.length - 1) ? commands[i + 1] : null;
if (curr && next && isMatrixParams(next)) {
paths.push(new UrlSegment(curr, stringify(next)));
i += 2;
} else {
// Adding the commands(`['some_user_id', 'payments']`) the the previous segments
// Which explains why you're getting the current behavior
paths.push(new UrlSegment(curr, {}));
i++;
}
}
return new UrlSegmentGroup(paths, {});
}
注意:本演练基于此ng-run 演示。
URL 可以具有以下结构:segment?queryParams#fragment
.
AnUrlSegmentGroup
可以有一个数组UrlSegments
和一个子UrlSegmentGroup
s 的对象:
export class UrlSegmentGroup {
/* ... */
parent: UrlSegmentGroup|null = null;
constructor(
public segments: UrlSegment[],
public children: {[key: string]: UrlSegmentGroup}) {
forEach(children, (v: any, k: any) => v.parent = this);
}
/* ... */
}
例如,我们可能有一个更复杂的 URL,例如foo/123/(a//named:b)
. 结果UrlSegmentGroup
将是这样的:
{
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: {} },
},
},
},
}
这将匹配这样的路由配置:
{
{
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',
},
}
你可以在这个StackBlitz中试验这个例子。
如您所见,UrlSegmentGroup
的孩子由 分隔()
。这些孩子的名字就是路由器插座。
在/(a//named:b)
,因为它使用了一个/
before (
,a
将segment of the primary outlet。//
是路由器插座的分隔符。最后,named:b
遵循这个结构:outletName:segmentPath
.
应该提到的另一件事是UrlSegment
'parameters
属性。除了位置参数(例如foo/:a/:b
),段可以有这样声明的参数segment/path;k1=v1;k2=v2
:
因此,anUrlTree
有 3 个重要属性:发出的 URL 的root
UrlSegmentGroup
、queryParams
对象和fragment
。
this.router.navigate('some_user_id', 'payments'])
有效,因为Router.navigate
最终会调用Router.createUrlTree
:
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}):
Promise<boolean> {
validateCommands(commands);
return this.navigateByUrl(this.createUrlTree(commands, extras), extras);
}
然后,const a = relativeTo || this.routerState.root;
将到达内部Router.createUrlTree
,并且由于没有relativeTo
(相对于RouterLink
),它将相对于 root ActivatedRoute
。
您可以通过在第一个命令的开头routerLink
添加,来获得相同的行为:/
[routerLink]="['/some_user_id', 'payments']"
推荐阅读
- c# - C# 将 XML 反序列化为模型类错误 -
没有预期 - unity3d - XR 设备 (Oculus) 在 Unity 的新输入系统中启动未配对
- javascript - Webpacker 找不到 application.js
- javascript - 从按钮列表中创建 Javascript 中的随机按钮
- java - 如何使用 SpringBoot + JPA 在 POSTGRES 中存储 JSON?
- java - 数据没有从 Firebase 读入我的 ArrayList
- python-3.x - 我必须在一行中每 3 个元素之后打印一个带有空格的网格,每三行之后打印一个空行,但我无法弄清楚代码
- c# - Brother Bpac SDK - .NET 核心和 .NET 5 的访问冲突,但不是 .NET 框架
- php - WooCommerce 根据特定产品的购买数量收取额外费用
- javascript - 向服务器中的所有成员发送消息不工作