带有来自ajax调用的数据的angular2引导程序

时间:2016-04-01 20:16:39

标签: typescript angular

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

的事情
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)
    });

这样可以正常工作,但我不喜欢两次使用依赖数组,是否有更简洁的方法呢?我可以在bootstrap之后向应用程序注入器添加内容吗?另外我注意到bootstrap函数返回一个promise,我可以使用这个promise来阻止应用程序的引导,直到我的ajax请求完成之后?

当然对于Injector,我只能使用MyService所需的那些依赖关系,但这使得它非常脆弱,你可以想象。

3 个答案:

答案 0 :(得分:5)

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

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

以下是一个示例实现:

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

答案 1 :(得分:1)

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

  

我可以在bootstrap之后向应用程序注入器添加内容吗?

是的,通过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仅适用于bootstrapthe difference between them给出明确理由的组件。

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

答案 2 :(得分:0)

您还可以使用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