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

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄2 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端