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

相关推荐
起床学FPGA2 小时前
AI回答问题之后,会自动跳到最下面的问题
开发语言·javascript·ecmascript
Fluxart.ai2 小时前
电商商品图审核怎么自动化?规则引擎、人工复核与发布门禁
java·前端·自动化
why技术2 小时前
AI 写的文章,可能都带着手敲一遍都去不掉的“隐形水印”。
前端·人工智能·后端
愚公搬代码3 小时前
【愚公系列】《Android应用案例开发大全》016-LBS类应用掌上杭州(辅助工具类的开发)
android·前端
CodeSheep3 小时前
又一个华为天才少年,离职了!
前端·后端·程序员
kyriewen3 小时前
面试官说"打开你的AI工具"——我才发现,他考的根本不是写代码
前端·人工智能·面试
IT_陈寒4 小时前
Vue的双向绑定把我坑惨了,原来这个场景不能用
前端·人工智能·后端
hunterandroid5 小时前
[Android 从零到一] Compose LazyColumn 性能优化:key、稳定性与重组治理
android·前端
lichenyang4535 小时前
从一次团队邀请开始:用 React、NestJS 与 Socket.IO 做可靠的实时通知
前端
vtian5 小时前
一篇文章吃透 Monorepo:pnpm + Turborepo + Changesets 全流程实战(含 8 个踩坑)
前端