【问题标题】:angular2 bootstrap with data from ajax call(s)带有来自 ajax 调用的数据的 angular2 引导程序
【发布时间】:2016-07-21 18:43:11
【问题描述】:

我想使用从服务中检索到的数据来引导我的应用程序。我正在做一些类似的事情

let dependencies = [
    //... a load of dependencies
    MyService
];

let injector = Injector.resolveAndCreate(dependencies);
let service: MyService = injector.get(MyService);

service.getData() // returns observable
    .toPromise()
    .then((d) => {
        // use data to append to dependencies

        bootstrap(App, dependencies)
    });

这很好用,但我不喜欢使用依赖数组两次,有没有更干净的方法呢?我可以在引导后向应用程序注入器添加东西吗?另外我注意到引导函数返回一个承诺,我可以使用这个承诺来阻止应用程序的引导,直到我的 ajax 请求完成之后?

当然,对于Injector,我只能使用MyService 所需的那些依赖项,但这使它变得非常脆弱,正如您可以想象的那样。

【问题讨论】:

    标签: typescript angular


    【解决方案1】:

    @Thierry(像往常一样)很好地回答了你的问题的核心,但我认为这值得单独注意:

    我可以在引导后向应用程序注入器添加东西吗?

    是的,通过在需要它们的组件的装饰器上的 providersviewProviders 中声明它们。例如:

    //main.ts
    bootstrap(MyComponent) //no dependencies declared
    
    
    //my.service.ts
    @Injectable class MyService { public getMessage = () => "foobar" }
    
    
    //my.component.ts
    @Component({ 
      selector: 'foo',
      providers: [MyService] 
      template: `<div>{{mySvc.getMessage()}}</div>` //displays foobar  
    })
    class MyComponent { 
        constructor(private mySvc: MyService){ }
    }
    

    注意providers 可用于指令和组件(这是DirectiveMetadata 的一个选项,ComponentMetadata 从中扩展),而viewProviders 仅可用于组件,原因很明显@ 987654325@。

    恕我直言,最好的做法是尽可能以这种方式注入依赖项,而不是这样做bootstrap,因为它允许您将给定依赖项的可用性范围限制为应用程序的一部分(即组件子树),您希望它在哪里可用。它也有利于渐进式加载,并避免在单个引导文件中配置无数不相关的注射剂的 SoC 气味。

    【讨论】:

      【解决方案2】:

      这里的问题是 Angular2 在引导主要组件之前不允许您访问应用程序引用及其注入器。在源代码中看到这一行:https://github.com/angular/angular/blob/master/modules/angular2/platform/browser.ts#L110

      一种方法可以是实现自定义引导而不是使用默认引导。类似的东西将应用程序的创建和应用程序组件的提升分开。这样你就可以在两个任务之间加载一些东西。

      这是一个示例实现:

      function customBoostrap(appComponentType, customProviders) {
        reflector.reflectionCapabilities = new ReflectionCapabilities();
        let appProviders =
          isPresent(customProviders) ? [BROWSER_APP_PROVIDERS, customProviders] : BROWSER_APP_PROVIDERS;
        var app = platform(BROWSER_PROVIDERS).application(appProviders);
      
        var service = app.injector.get(CompaniesService);
      
        return service.getCompanies().flatMap((companies) => {
          var companiesProvider = new Provider('companies', { useValue: data });
          return app.bootstrap(appComponentType, [ companiesProvider ]);
        }).toPromise();
      }
      

      并以这种方式使用它:

      customBoostrap(AppComponent, [
        HTTP_PROVIDERS,
        CompaniesService
      ]);
      

      公司将自动在组件内进行注入,例如:

      @Component({
        (...)
      })
      export class AppComponent {
        constructor(@Inject('companies') companies) {
          console.log(companies);
        }
      }
      

      查看相应的 plunkr:https://plnkr.co/edit/RbBrQ7KOMoFVNU2ZG5jM?p=preview

      目前,它有点 hacky,但这种方法可以作为功能请求提出......

      编辑

      查看ApplicationRef 类的文档后,我发现有一个更简单的解决方案;-)

      var app = platform(BROWSER_PROVIDERS)
         .application([BROWSER_APP_PROVIDERS, appProviders]);
      
      service.getCompanies().flatMap((companies) => {
        var companiesProvider = new Provider('companies', { useValue: data });
        return app.bootstrap(appComponentType, [ companiesProvider ]);
      }).toPromise();
      

      这里是对应的 plunkr:https://plnkr.co/edit/ooMNzEw2ptWrumwAX5zP?p=preview

      【讨论】:

        【解决方案3】:

        您还可以使用APP_INITIALIZER 注入令牌,也可以让它并行调用多个异步资源:

        import { NgModule, APP_INITIALIZER } from '@angular/core';
        import { HttpClientModule } from "@angular/common/http";
        
        import { AppLoadService } from './app-load.service';
        
        export function init_app(appLoadService: AppLoadService) {
            return () => appLoadService.initializeApp();
        }
        
        export function get_settings(appLoadService: AppLoadService) {
            return () => appLoadService.getSettings();
        }
        
        @NgModule({
          imports: [HttpClientModule],
          providers: [
            AppLoadService,
            { provide: APP_INITIALIZER, useFactory: init_app, deps: [AppLoadService], multi: true },
            { provide: APP_INITIALIZER, useFactory: get_settings, deps: [AppLoadService], multi: true }
          ]
        })
        export class AppLoadModule { }
        

        来源:Angular 4 Tutorial – Run Code During App Initialization

        另一篇有趣的文章:Hook into Angular’s Initialization Process

        【讨论】:

          猜你喜欢
          • 2017-04-30
          • 2018-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多