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

相关推荐
YGY Webgis糕手之路2 小时前
OpenLayers 综合案例-轨迹回放
前端·经验分享·笔记·vue·web
90后的晨仔3 小时前
🚨XSS 攻击全解:什么是跨站脚本攻击?前端如何防御?
前端·vue.js
Ares-Wang3 小时前
JavaScript》》JS》 Var、Let、Const 大总结
开发语言·前端·javascript
90后的晨仔3 小时前
Vue 模板语法完全指南:从插值表达式到动态指令,彻底搞懂 Vue 模板语言
前端·vue.js
德育处主任3 小时前
p5.js 正方形square的基础用法
前端·数据可视化·canvas
烛阴3 小时前
Mix - Bilinear Interpolation
前端·webgl
90后的晨仔3 小时前
Vue 3 应用实例详解:从 createApp 到 mount,你真正掌握了吗?
前端·vue.js
德育处主任3 小时前
p5.js 矩形rect绘制教程
前端·数据可视化·canvas
前端工作日常4 小时前
我学习到的babel插件移除Flow 类型注解效果
前端·babel·前端工程化
SY_FC4 小时前
uniapp input 聚焦时键盘弹起滚动到对应的部分
javascript·vue.js·elementui