我正在做一个项目,将一个AngularJS应用转换为Angular,我面临着一个关于路由的阻塞。
TLDR。我需要在使用路由模块之前,根据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应用的基本工作流程。
angular.bootstrap(document.getElementById('mainElementId'),[‘appName']);
这是因为AngularJS的行为是不在加载时调用.config(),而是在angular应用的bootstrap时调用,我们将其推迟到API响应之后。
今天工作的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 }
];
路由是按顺序匹配的,所以 "已知 "的路由应该先走。任何带有单段的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
是一些验证应用名称的函数。
如何在路由器导航执行甚至初始化之前,根据API响应创建路由?
有两种方法可以做到这一点。
第一种方法是使用 组成部分 来显示所有的动态路由。首先定义所有静态路由,最后将动态路由路由路由到 DynamicComponent
与路由参数 id
. 在 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
})
}
}
第二种方法是使用 服务项目 来过滤所有未知路由。首先定义所有的静态路由,最后将动态路由路由路由到 DynamicComponent
过滤的 DynamicRouteService
它实现了 CanActivate
. 在 DynamicRouteService
,我们映射出 next.params
返回一个 Observable<boolean>
到Router模块,并将保持路由,直到满足可观察的条件。
在 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] },
];
注意:一定要加上 DynamicRouteService
到 providers
在 app.module.ts
在 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的解决方案简单多了。
我已经在中回答了这个问题。在初始化之前从REST服务中获取Angular负载路由。