在 Angular 中访问 URL 有多种方式。下面我将解释为什么我会选择使用 Router.url
而不是其他方法。
Location.path()
如文档所述,这个 API
会为当前位置的 URL 路径进行标准化处理。
你需要注意以下几种情况:
根路径返回空值
对于像 http://localhost:4200/#/
这样的 URL,它会返回 ''
,而 Router.url
则会返回 /
。
当位置由浏览器外部更改时,不会等待路由守卫检查
如果你直接在浏览器中访问一个未授权页面,比如 http://localhost:4200/#/unauthorized
,并且正在执行守卫检查 ,那么 Location.path()
会立即返回 /unauthorized
。而 Router.url
在守卫检查过程中会返回 /
,在检查通过后才会返回 /unauthorized
。我认为在大多数情况下,Router.url
的行为更合理。但在某些特定场景下,我们确实需要 Location.path()
的这种行为。
一个类似的用例是用户直接点击浏览器的"返回"按钮。
这两种情况都会直接更改地址栏中的路径,而不是等待 Angular 的路由守卫流程完成,因为这些操作是由浏览器直接触发的,超出了 Angular 的控制范围。
在大多数正常情况下,它的行为与 Router.url
是一致的,因为用户是在 Angular 内部进行导航,Angular 会在路由守卫完成后更新地址栏内容。
Router.events
配合 NavigationEnd
语法如下。你可以在组件或服务中使用它:
ts
@Injectable({
providedIn: "root",
})
export class RouterUtilitiesService {
url$ = this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
map((event) => event.url)
);
constructor(private router: Router) {}
}
ts
@Component({
template: ``,
})
export class ChildComponent {
url$ = this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
map((event) => event.url)
);
constructor(
private router: Router,
private routerUtilitiesService: RouterUtilitiesService,
private destroyRef: DestroyRef
) {
this.routerUtilitiesService.url$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((v) => console.log(`routerUtilitiesService.url$: ${v}`));
this.url$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((v) => console.log(`url$: ${v}`));
}
ngOnInit() {}
}
export const routes: Routes = [
{
path: "child",
component: ChildComponent,
children: [
{
path: "grandchild1",
component: GrandChildComponent,
},
{
path: "grandchild2",
component: GrandChildComponent,
},
],
},
];
在上面的例子中,如果你第一次访问 /child/grandchild1
,你将看不到任何日志输出,因为在 ChildComponent
激活之前,NavigationEnd
事件已经触发了。当然,在 ChildComponent
内部导航时你会看到日志。
要解决这个问题,你可以添加 startWith(this.router.url)
来确保首次加载也能获取到当前 URL。
ActivatedRoute.snapshot.url
如文档所述,ActivatedRoute
提供对已加载组件所关联的路由信息的访问。
它不提供完整的 URL,只提供与组件相关联的路由部分。此外,你不应该在组件之外使用它。例如:
ts
@Injectable({
providedIn: "root",
})
export class RouterUtilitiesService {
constructor(public activatedRoute: ActivatedRoute) {}
}
@Component({
template: ``,
})
export class GrandChildComponent {
constructor(
private routerUtilitiesService: RouterUtilitiesService,
private activatedRoute: ActivatedRoute
) {}
ngOnInit() {
console.log(
`activatedRoute.snapshot.url: `,
this.activatedRoute.snapshot.url.toString()
);
console.log(
`routerUtilitiesService.activatedRoute.snapshot.url: `,
this.routerUtilitiesService.activatedRoute.snapshot.url.toString()
);
}
}
export const routes: Routes = [
{
path: "child",
component: ChildComponent,
children: [
{
path: "grandchild1",
component: GrandChildComponent,
},
{
path: "grandchild2",
component: GrandChildComponent,
},
],
},
];
我展示了两种 activatedRoute.snapshot.url
的使用方式:一种来自组件,另一种来自 routerUtilitiesService.activatedRoute
。当你访问 http://localhost:4200/#/child/grandchild1
时,输出结果为:
activatedRoute.snapshot.url: grandchild1
routerUtilitiesService.activatedRoute.snapshot.url:
如你所见,在 GrandChildComponent
中使用的 activatedRoute
返回的是 grandchild1
;如果在 ChildComponent
中使用,则返回 child
。
而来自 routerUtilitiesService.activatedRoute
的值则显得很奇怪。如果你想在服务中提取一个类似 isGrandChild1Page
的工具函数,你就必须每次都在组件中注入 activatedRoute
并将其传递给服务的方法才能得到预期结果。例如:
ts
@Injectable({
providedIn: "root",
})
export class RouterUtilitiesService {
constructor() {}
isGrandChild1Page(activatedRoute: ActivatedRoute) {
return activatedRoute.snapshot.url[0].path === "grandchild1";
}
}
@Component({
template: ``,
})
export class GrandChildComponent {
constructor(
private routerUtilitiesService: RouterUtilitiesService,
private activatedRoute: ActivatedRoute
) {}
ngOnInit() {
console.log(
this.routerUtilitiesService.isGrandChild1Page(this.activatedRoute)
);
}
}
这种方式略显不便。而如果使用 Router.url
,代码可以简化为:
ts
@Injectable({
providedIn: "root",
})
export class RouterUtilitiesService {
constructor(private router: Router) {}
isGrandChild1PageWithRouter() {
return this.router.url.split("?")[0].endsWith("/grandchild1");
}
}
@Component({
template: ``,
})
export class GrandChildComponent {
constructor(
private routerUtilitiesService: RouterUtilitiesService,
) {}
ngOnInit() {
console.log(this.routerUtilitiesService.isGrandChild1PageWithRouter());
}
}
Router.routerState.snapshot.url
如文档所述,Router.routerState
表示该 NgModule 中当前的路由状态。它以激活路由的树结构形式表示路由器的状态。
而 Router.url
表示当前的 URL。
我不确定它们是否完全相同。从我的测试来看,它们是一致的。但我认为你不太可能使用这个方式,因为 Router.url
明显更加简单。
结论
好了,我已经总结了我在尝试获取 Angular 页面 URL 时考虑过的所有 API。根据你的具体需求,你可能会选择不同的方法,也可能还有本文未列出的其他 API。无论如何,请谨慎使用。
截至目前,我会默认使用 Router.url
。