我的 Angular 偏好:使用 Router.url 来访问 URL,而不是其他方式

在 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 会在路由守卫完成后更新地址栏内容。


语法如下。你可以在组件或服务中使用它:

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

相关推荐
学嵌入式的小杨同学4 小时前
从零打造 Linux 终端 MP3 播放器!用 C 语言实现音乐自由
linux·c语言·开发语言·前端·vscode·ci/cd·vim
weixin_425543734 小时前
TRAE CN3.3.25 构建的Electron简易DEMO应用
前端·typescript·electron·vite·nestjs
Mr Xu_5 小时前
【Vue3 + ECharts 实战】正确使用 showLoading、resize 与 dispose 避免内存泄漏
前端·信息可视化·vue·echarts
0思必得05 小时前
[Web自动化] Selenium设置相关执行文件路径
前端·爬虫·python·selenium·自动化
雯0609~5 小时前
hiprint:实现项目部署与打印1-官网提供普通html版本
前端·html
yuezhilangniao5 小时前
AI智能体全栈开发工程化规范 备忘 ~ fastAPI+Next.js
javascript·人工智能·fastapi
不绝1916 小时前
UGUI——进阶篇
前端
Exquisite.6 小时前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
铅笔侠_小龙虾7 小时前
Flutter Demo
开发语言·javascript·flutter
2501_944525547 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter