Angular:如何根据 API 响应路由到不同的组件?

Angular: How to route to different components, depending on API response?

我想知道是否/如何在 Angular 10.

中实施特定的路由策略

为了解释,举这个简化的例子:

我有一个包含 4 个组件的 Angular 应用程序:

我想实现如下路由策略

名称“millerh”和“math1”是唯一的,没有同时映射到学生和课程的 ID。 (编辑:也不会有名为“学生”或“课程”的学生或课程)

背景:链接会显示在另一个应用程序中,链接不能点击,不能copy/pasted,所以我希望路径越短越好。此外,高级用户更愿意直接输入 URL 而不是转到列表页面并搜索他们想要查看其详细信息的学生/课程。

为了实现这一点,我想在路由期间进行一个backend/API调用。 API 响应将表明,即“millerh”是学生、课程还是两者都不是。 取决于我想导航到相应的组件,并传递名称student/course 以及组件。我想避免重定向,即从 myapp.com/millerh 到 myapp.com/students/millerh,以避免同一资源有多个有效路径。

如何从开箱即用的 angular 路由模块开始?

非常感谢任何提示和建议!

我认为 Angular router 无法区分 millerh 是学生还是 math1 是课程,我认为你应该选择类似的东西:
myapp.com/students/millerh
myapp.com/courses/math1

编辑

假设您的一个用户想要搜索一个学生,他将输入 myapp.com/students/<studentName>,然后该应用将导航至 students 组件,而 studentName 将是可用作路由参数,然后您可以使用 ngOnInit 生命周期挂钩对后端的学生端点进行 API 调用。

编辑编号 2

无需在 URL 中键入 students/courses 的解决方案,但在 URL 解决后您仍然可以使用它们

假设您一开始不想区分学生路线和课程路线。
想象你的用户类型 myapp.com/<someValue>,现在你不知道他在找什么,学生还是课程,一开始他会登陆你的根组件,在这个组件中你可以使用 ngOnInit 来拨打 API 电话。
首先对您的学生端点进行 API 调用,如果有学生,请让您的应用程序以编程方式导航至 myapp.com/students/<someValue>.
如果找不到学生,请 API 调用您的课程端点,如果有课程,请让您的应用以编程方式导航至 myapp.com/courses/<someValue>.
最终会有对 url 课程学生的引用,但对您的用户来说会更容易,因为他们可以直接键入 myapp.com/<someValue>.

无需在 URL 中键入 students/courses 的解决方案,你也不会将它们放在最后的 URL 中

您可以将它们放在一个页面中,而不是将您的学生组件和您的课程放在两个不同的页面中,在您的情况下可能是根组件,而不是重定向到 studentscourse,您可以根据哪个 API 调用返回值来显示一个或另一个。

好的,我找到了一个我非常喜欢的工作解决方案:它似乎工作得很好,而且感觉不太hack。它并不完全符合任何已发布的建议 - 但感谢所有回复和评论,因为它们确实帮助我找到了最终解决方案!

最终的解决方案在其核心中使用了一个实现 CanActivate 的守卫,结合守卫内部的 router.resetConfig()router.navigate()

作为我的解决方案的入口点,我使用标准路由模块。它对我的用例仍然有用,因为有些组件具有单个静态路由。

文件:app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './features/home/home.component';
import { StudentListComponent} from './features/student-list/student-list.component';
import { CourseListComponent} from './features/course-list/course-list.component';
import { LoadingComponent } from './features/loading/loading.component';
import { NotFoundComponent } from './features/not-found/not-found.component';
import { DynamicRouteGuard } from './guards/dynamic-route.guard';

const routes: Routes = [
  { 
    path: '/', 
    component: HomeComponent
  },
  {
    path: 'students',
    component: StudentListComponent
  },
  {
    path: 'courses',
    component: CourseListComponent
  },
  {
    path: 'not-found',
    component: NotFoundComponent
  },
  {
    path: '**',
    canActivate: [ DynamicRouteGuard ],
    component: LoadingComponent,
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

路径 /、students、courses 和 not-found 是大多数 Angular 应用程序中的普通静态路由。任何与这些不匹配的路由都将被底部的通配符路由处理。该路由加载一个组件,该组件可能包含一个加载微调器。当 Guard 异步地对后端进行 API 调用以确定加载哪个组件时,它将是可见的。

文件:动态-route.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { EntityTypeService } from '../../services/entity-type.service';
import { IEntityTypeModel } from '../../models/entity-type.model';
import { EntityType } from '../../models/entity-type.enum';
import { StudentDetailComponent } from '../features/student-detail/student-detail.component';
import { CourseDetailComponent } from '../features/course-detail/course-detail.component';

@Injectable({
  providedIn: 'root'
})
export class DynamicRouteGuard implements CanActivate {
  constructor(
    private entityTypeService : EntityTypeService,
    private router : Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 
    Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // At this point, only routes with a single path element are considered valid routes.
    if (route.url.length != 1) {
      this.router.navigate(['not-found']);
      return false;
    }

    let path = route.url[0].toString();

    // Ask backend, what kind of entity the requested path matches to
    this.entityTypeService.getEntityTypeForPath(path).subscribe(response => {
      let entityTypeModel = response as IEntityTypeModel;

      // If backend did not recognize path --> redirect to page not found component.
      if (entityTypeModel.entityType === EntityType.Unknown) {
        this.router.navigate(['not-found']);
      }

      // Build a new routes array. Slicing is required as wildcard route
      // should be omitted from the new routes array (risk of endless loop)
      let routes = this.router.config;
      let newRoutes = routes.slice(0, routes.length - 1);

      // Add a new route for the requested path to the correct component.
      switch(entityTypeModel.entityType) {
        case EntityType.Student:
          newRoutes.push({ path: path, component: StudentDetailComponent, data: { resourceName: path } });
          break;
        case EntityType.Course:
          newRoutes.push({ path: path, component: CourseDetailComponent, data: { resourceName: path } });
          break;
        default:
          this.router.navigate(['not-found']);
          return;
      }

      // Reload routes and navigate.
      this.router.resetConfig(newRoutes);
      this.router.navigate([path]);
    });

    // Guard always returns true and loads LoadingComponent while API 
    // request is being executed.
    return true;
  }
}

在守卫中,可以访问请求的路由,并且可以轻松注入 API 服务。使用该服务,后端在数据库中查找路径,并且 returns 一个枚举值指示它是学生、课程还是两者都不是。根据这一点,一个新的路由被添加到 Routes 数组,对于那个特定的 student/course 名称,链接到匹配的组件。之后重新加载路由,路由器可以直接导航到正确的组件。