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的基本工作流程:
- AngularJS 应用程序未立即使用绑定到元素
ng-app, like 常做。
- 在页面加载时从 API 收到原始(或 jQuery)响应。
- 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过滤所有未知路由。首先定义所有静态路由,最后将动态路由路由到由实现 CanActivate
的 DynamicRouteService
过滤的 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.
的解决方案要简单得多
我已经在:
我正在从事一个将 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的基本工作流程:
- AngularJS 应用程序未立即使用绑定到元素 ng-app, like 常做。
- 在页面加载时从 API 收到原始(或 jQuery)响应。
- 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过滤所有未知路由。首先定义所有静态路由,最后将动态路由路由到由实现 CanActivate
的 DynamicRouteService
过滤的 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.
的解决方案要简单得多我已经在: