【问题标题】:Angular, load json file before AppRoutingModule loadsAngular,在 AppRoutingModule 加载之前加载 json 文件
【发布时间】:2022-01-28 16:22:15
【问题描述】:

我想在 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 文件?

提前致谢

【问题讨论】:

    标签: angular typescript dependency-injection xmlhttprequest


    【解决方案1】:

    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
            );
        }
    
    }
    
    1. 我们在服务构造函数中注入路由,

    2. 一旦我们检索到我们的后端模型,我们就会从中创建一个 observable(使 DSL 更容易并更好地与 load() 函数集成,我们很快就会看到,但如果您愿意,可以使用普通循环来完成),

    3. 我们将后端路由规范转换为实际路由(稍后我们会解释),

    4. 我们用新的路线扁平化现有/引导路线,

    5. 最后我们用新路由更新路由器。请注意,这也是您可以存储您加载的路线并将它们存储在 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
    }
    
    1. 工厂是一个普通函数,它返回一个返回承诺的函数,它将我们在提供者注册期间绑定在应用程序模块中的服务/提供者作为参数,

    2. 我们要等待路由加载,所以我们在应用程序初始化期间调用加载函数来执行它,

    3. 我们将 observable 转换为 Promise,以确保 Angular 在完成开始之前等待此逻辑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-27
      • 2022-11-30
      • 1970-01-01
      • 1970-01-01
      • 2022-11-24
      • 2019-03-19
      相关资源
      最近更新 更多