我的 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

相关推荐
姑苏洛言25 分钟前
基于微信公众号小程序的课表管理平台设计与实现
前端·后端
烛阴38 分钟前
比UUID更快更小更强大!NanoID唯一ID生成神器全解析
前端·javascript·后端
Alice_hhu1 小时前
ResizeObserver 解决 echarts渲染不出来,内容宽度为 0的问题
前端·javascript·echarts
charlee442 小时前
解决Vditor加载Markdown网页很慢的问题(Vite+JS+Vditor)
javascript·markdown·cdn·vditor
逃逸线LOF2 小时前
CSS之动画(奔跑的熊、两面反转盒子、3D导航栏、旋转木马)
前端·css
萌萌哒草头将军3 小时前
⚡️Vitest 3.2 发布,测试更高效;🚀Nuxt v4 测试版本发布,焕然一新;🚗Vite7 beta 版发布了
前端
技术小丁3 小时前
使用 HTML + JavaScript 在高德地图上实现物流轨迹跟踪系统
前端·javascript·html
小小小小宇3 小时前
React 并发渲染笔记
前端
stark张宇3 小时前
Web - 面向对象
前端·javascript
代码的余温4 小时前
打造极致计算器:HTML+Tailwind+DaisyUI实战
javascript·css·html