angular - Angular,在 AppRoutingModule 加载之前加载 json 文件
问题描述
我想在 App 启动之前加载一个 json 配置文件,这样我就可以根据配置选项提供一些服务或其他服务。我使用 APP_INITIALIZER 令牌加载它并且一切正常,直到我需要根据相同的配置选项进行条件路由。我试图通过模块中的 APP_INITIALIZER 加载配置文件并将此模块导入 AppRoutingModule 和主 AppModule 确保提供相同的实例以不加载配置文件两次。
@NgModule({
declarations: [],
imports: [
CommonModule
],
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (cs: ConfigService, http: HttpClient) => cs.loadConfig(http),
deps: [ConfigService, HttpClient],
multi: true
},
]
})
export class CoreModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
ConfigService
]
};
}
}
这是配置服务:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable()
export class ConfigService {
proccesId: number;
constructor() {}
loadConfig(http: HttpClient) {
return () => {
return (http.get('/assets/app-config.json') as Observable<any>)
.toPromise()
.then((config) => {
this.processId = config.processId
});
};
}
}
这是我在 AppModule 中导入 CoreModule 的方法(在 AppRoutingModule 中以相同的方式导入):
export function OptionServiceFactory (cs: ConfigService, injector: Injector): OptionService {
if(cs.processId === 1) {
return injector.get(OneOptionService);
} else {
return injector.get(OtherOptionService);
}
}
@NgModule({
imports: [
CoreModule.forRoot(),
],
providers: [
OneOptionService,
OtherOptionService,
{
provide: OptionService,
useFactory: OptionServiceFactory,
deps: [ConfigService, Injector]
}],
bootstrap: [AppComponent]
})
export class AppModule { }
这适用于主 AppModule,应用程序等待 ConfigService 加载 JSON 配置文件,因此我能够在工厂中使用 JSON 填充的 ConfigService。但是 AppRoutingModule 不会等待,因此任何访问 ConfigService 属性的操作都会失败。这是 AppRoutingModule:
const routes: Routes = [];
function routesFactory(cs: ConfigService): Routes {
let rutas: Routes = [
{ path: '', redirectTo: '/same-path', pathMatch: 'full' }
];
/* This fails because processId is undefined because JSON file has not been loaded jet */
if(cs.processId === 1) {
rutas.push({ path: 'same-path', component: OneComponent });
} else {
rutas.push({ path: 'same-path', component: OtherComponent });
}
return rutas;
}
@NgModule({
imports: [
CoreModule.forRoot(),
RouterModule.forRoot(routes)
],
exports: [RouterModule],
providers: [
{ provide: ROUTES, multi: true, useFactory: routesFactory, deps: [ConfigService] }
]
})
export class AppRoutingModule { }
作为一种解决方法,我进行了同步 http 调用以在 AppRoutingModule 中加载 json 文件。现在 AppRoutingModule 中的 routesFactory 是:
function routesFactory(cs: ConfigService): Routes {
let rutas: Routes = [
{ path: '', redirectTo: '/same-path', pathMatch: 'full' }
];
/* Syncronous http request */
var request = new XMLHttpRequest();
request.open('GET', '/assets/app-config.json', false);
request.send(null);
if (request.status === 200) {
let config = JSON.parse(request.responseText);
cs.processId = config.processId;
}
/* Now it doesn't fail because we loaded json syncronously */
if(cs.processId === 1) {
rutas.push({ path: 'same-path', component: OneComponent });
} else {
rutas.push({ path: 'same-path', component: OtherComponent });
}
return rutas;
}
它有效,但不鼓励这样做,现在我在浏览器控制台中收到警告。
[弃用] 主线程上的同步 XMLHttpRequest 已弃用,因为它对最终用户的体验产生不利影响。如需更多帮助,请查看https://xhr.spec.whatwg.org/。
有没有办法在 AppRoutingModule 加载之前加载 JSON 文件?
提前致谢
解决方案
Romain Manni-Bucau在他的博客文章https://rmannibucau.metawerx.net/angular-ng-10-create-update-route-at-runtime.html中解释了如何以编程方式加载和更新路由
我在这里复制基本步骤,以防博客不可用。
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router, Routes } from '@angular/router';
import { from } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class RouteLoader {
constructor(
private client: HttpClient,
private router: Router) /* 1 */ { }
public load() {
return this.client.get('/my-routes')
.pipe(switchMap(json => this.createRoutes(json)));
}
private createRoutes(json) {
return from(json.routeSpecs).pipe( // 2
map(spec => this.toRoutes(spec)), // 3
map(routes => ([ // 4
...this.router.config,
...routes,
])),
map(newRoutes => this.router.resetConfig(newRoutes)) // 5
);
}
}
我们在服务构造函数中注入路由,
一旦我们检索到我们的后端模型,我们就会从中创建一个 observable(使 DSL 更容易,并更好地与 load() 函数集成,我们很快就会看到,但如果您愿意,可以使用普通循环来完成),
我们将后端路由规范转换为实际路由(我们将在之后解释),
我们用新的路线扁平化现有/引导路线,
最后,我们用新路由更新路由器。请注意,这也是您可以存储您加载的路线并将它们存储在 RouteLoader 服务中的地方。如果您从获取的内容动态构建菜单,并且它可以将服务注入任何组件并动态创建菜单(例如使用 *ngFor),而无需使用全局变量(作为原始提取),这将非常有用选项将需要)。
providers: [ // 1 { provide: APP_INITIALIZER, useFactory: ensureRoutesExist, // 2 multi: true, deps: [HttpClient, RouteLoader], // 3 } ], bootstrap: [AppComponent] }) export class AppModule { }
1.我们使用令牌 APP_INITIALIZER 注册一个自定义提供程序(确保将 multi 设置为 true,因为您可以为每个应用程序获取多个初始化程序),
2.我们将其工厂绑定到自定义方法,
3.我们绑定工厂参数——在这种情况下这是一个普通的函数,需要这个显式的注入绑定。
export function ensureRoutesExist( // 1
http: HttpClient,
routeLoader: RouteLoader) {
return () => routeLoader.load() // 2
.toPromise(); // 3
}
工厂是一个普通函数,它返回一个返回承诺的函数,它将我们在提供者注册期间绑定在应用程序模块中的服务/提供者作为参数,
我们想等待路由加载,所以我们在应用程序初始化期间调用 load 函数来执行它,
我们将 observable 转换为 Promise,以确保 Angular 在完成开始之前等待此逻辑。
推荐阅读
- c++ - 我需要一种机制来允许使用 C++ 运行时在 ANTLR4 中处理包含文件
- python - python pygame蒙版碰撞
- php - Laravel 中的 Symfony\Process 包(用于 python 文件)
- python - 调用 focus_force 时,Tkinter 组合框不起作用
- python - 如何更新亚秒时间序列熊猫数据框中的单元格值
- c# - 仅当不为空时才包含在 where 语句中
- c# - 抱歉,当我使用 C# Blazor 单击链接时,此地址页面上没有显示任何内容
- wordpress - 解析错误:语法错误,第 57 行 header.php 中出现意外的“}”
- sqlite - 使用 SQLite 计算两个经纬度点之间的距离(几乎是 Haversine 公式)
- ios - 删除事件后表格视图中的奇怪行为