Angular / Angular 9中如何在初始化前动态创建路由

How to dynamically create routes before initialization in Angular / Angular 9

我正在从事一个将 AngularJS 应用程序转换为 Angular 的项目,但我遇到了有关路由的障碍。

TL/DR: 我需要在使用路由模块之前根据 API 响应定义路由。

AngularJS 中的工作场景: (下面进一步的伪代码排序)

每个人都有几个基本路由,它们以标准 AngularJS 方式定义:

/home
/settings
...etc

然后是基于API响应

创建的动态路由
/purchase-requests
/invoices
/godzilla
...etc. Content doesn’t matter, basically, a dynamic list of routes that an existing API gives as an array of strings

现有AngularJS app的基本工作流程:

  1. AngularJS 应用程序未立即使用绑定到元素 ng-app, like 常做。
  2. 在页面加载时从 API 收到原始(或 jQuery)响应。
  3. AngularJS 应用程序初始化使用:
 angular.bootstrap(document.getElementById('mainElementId'),[‘appName']);

这是因为 AngularJS 的行为不是在加载时调用 .config() 而是在 angular 应用程序的 bootstrap 上,我们推迟到 [=72] =]响应。

今天可用的示例 AngularJS:

<script>

  let appList = [];
  const mainApp = angular.module('mainApp', ['ngRoute']);


  // Controllers
  mainApp.controller('mainController', mainController);
  mainApp.controller('homeController', homeController);
  mainApp.controller('appListController', appListController);
  mainApp.controller('appSingleController', appSingleController);
  mainApp.controller('errorController', errorController);

  // config will not be called until the app is bootstrapped
  mainApp.config(function($routeProvider) {

    // Default routes that everyone gets
    $routeProvider.when('/', { templateUrl: 'views/home.html', controller: 'homeController'});
    $routeProvider.when('/home', { templateUrl: 'views/home.html', controller: 'homeController'});

    // Add the dynamic routes that were retreived from the API
    for (let appName in appList) {
      $routeProvider.when(`/${appName}`, { templateUrl: 'views/app-list.html', controller: 'appListController'});
      $routeProvider.when(`/${appName}/new`, { templateUrl: 'views/app-single.html', controller: 'appSingleController'});
      $routeProvider.when(`/${appName}/view/:itemNumber`, { templateUrl: 'views/app-single.html', controller: 'appSingleController'});
    }

    $routeProvider.otherwise({ templateUrl: 'views/error.html', controller: 'errorController'});
  });



  $(document).ready(function() {
    const options = {
      type: 'GET',
      url: '/api/apps/getAvailableApps',
      success: onAppSuccess,
    };
    $.ajax(options);
  });

  function onAppSuccess(response) {
    appList = response.appList;
    angular.bootstrap(document.getElementById('mainApp'), ['mainApp']);
  }

</script>

<!-- Typically, you bind to the app using ng-app="mainApp" -->
<div id="mainApp" class="hidden" ng-controller="mainController">

  <!-- Route views -->
  <div ng-view></div>

</div>

在Angular 9(或者,似乎是Angular的任何最新版本)中,路由在主要组件初始化之前在路由模块中定义:

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: '', component: DashboardComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
];

使用router.resetConfig无效

假设我让主模块首先加载 API 配置,然后根据响应使用 resetConfig。如果用户加载的第一个页面是 //home 或其他预定义路由之一,这将非常有效:创建新的动态路由并导航到它们。

但是,如果用户直接导航到未预定义的路由(比如 /godzilla),路由器甚至不允许加载页面(或者)如果设置了通配符路由,则会显示 404 . 主要组件中的 ngOnInit() (我试图用它来加载 API 响应)从来没有机会 运行.

问题是:如何在路由器导航执行甚至初始化之前根据API响应创建路由?

我添加动态路由的方法是使用参数预定义路由url模板。

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: '', component: DashboardComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
  { path: ':appName', canActivate: AppGuard, children: [
    { path: '', component: AppListComponent },
    { path: 'new', component: 'NewAppComponent' },
    { path: 'view/:itemNumber', component: AppSingleComponent }
  ] },
  { path: '**', component: ErrorComponent }
];

路由按顺序匹配,因此 "known" 路由应该先匹配。任何 URL 具有与 "known" 路由不匹配的单个分段的任何 URL 将与 :appName 匹配。您可以声明一个守卫来验证 :appName 参数是否有效。如果不是,'**' 路由将被匹配。

守卫看起来像这样:

@Injectable({ providedIn: 'root' })
export class AppGuard implements CanActivate {
  constructor(private appService: AppService) {
  }

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const appName = route.params.appName;
    return this.appService.isValidAppName(appName);
  }
}

其中 appService.isValidAppName 是一些验证应用程序名称的函数。

How can I create routes based on the API response before the router navigation is executed or even initialized?

有两种方法可以做到这一点。

第一种方式是使用Component显示所有动态路由。首先定义所有静态路由,最后使用路由参数 id 将动态路由路由到 DynamicComponent。在 DynamicComponent 中,我们使用 ActivatedRoute 获取路由参数,并在失败时使用 Router 导航到 404 路由。

app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: "prefix" },
  { path: 'login', component: LoginComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
  { path: '404', component: PageNotFoundComponent },
  { path: ':id', component: DynamicComponent },
];

DynamicComponent

constructor(private aroute: ActivatedRoute, private router: Router) { }

ngOnInit(): void {

  this.aroute.params.pipe(first()).subscribe((param) => {
    console.log(param.id)

    ...   // make any API call with param.id and get a response as promise

    .then( (response) => {

       ...    // do whatever you want to do on success

    })
    .catch( (error) => {

       console.error(error);
       this.router.navigate(['404']);    // route to 404 on failure

    })


  }
}

第二种方式是使用Service过滤所有未知路由。首先定义所有静态路由,最后将动态路由路由到由实现 CanActivateDynamicRouteService 过滤的 DynamicComponent。在 DynamicRouteService 中,我们将 next.params 映射到 return 并将 Observable<boolean> 映射到 Router 模块,并将保持路由直到 observable 完成。

app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: "prefix" },
  { path: 'login', component: LoginComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
  { path: '404', component: PageNotFoundComponent },
  { path: ':id', component: DynamicComponent, canActivate: [DynamicRouteService] },
];

注意:确保在app.module.ts

中将DynamicRouteService添加到providers

dynamic-route.service.ts

export class DynamicRouteService implements CanActivate {

  constructor(private router: Router) { }

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

    return next.params.pipe(first()).pipe(map(param) => {
      console.log(param.id)

      return ...   // make any API call with param.id and get a response as promise

      .then( (response) => {

         ...    // do whatever you want to do on success
         return true;

      })
      .catch( (error) => {

         console.error(error);
         this.router.navigate(['404']);    // route to 404 on failure
         return false;

      }))

    }
  }
}

感谢您的回复。

我最终以不同的方式解决了这个问题

我本来打算使用 "DynamicRouter" 组件,但发现使用 APP_INITIALIZER.

的解决方案要简单得多

我已经在: