angular2 bootstrap with data from ajax call(s)

Solution 1:

The problem here is that Angular2 doesn't give you access to the application reference and its injector before bootstrapping the main component on it. See this line in the source code: https://github.com/angular/angular/blob/master/modules/angular2/platform/browser.ts#L110.

An approach could be to implement a custom bootstrap instead of using the default one. Something like that that splits the application creation and the boostrapping on the application component on it. This way you will be able to load something between the two tasks.

Here is a sample implementation:

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();
}

and use it this way:

customBoostrap(AppComponent, [
  HTTP_PROVIDERS,
  CompaniesService
]);

Companies will be automatically available for injection within the component for example:

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

See this corresponding plunkr: https://plnkr.co/edit/RbBrQ7KOMoFVNU2ZG5jM?p=preview.

At this time, it's a bit hacky but such approach could proposed as a feature request...

Edit

After having a look at the doc for the ApplicationRef class, I saw that there is a simpler solution ;-)

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();

Here is the corresponding plunkr: https://plnkr.co/edit/ooMNzEw2ptWrumwAX5zP?p=preview.

Solution 2:

@Thierry (as usual) has answered the heart of your question well, but I think this is worth noting separately:

Can I add things to the application injector after bootstrap?

Yes, by declaring them in providers or viewProviders on the decorators of the components that require them. e.g:

//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){ }
}

Note that providers can be used on directives as well as components (it's an option on DirectiveMetadata, from which ComponentMetadata extends), while viewProviders is only available on components for reasons that are clear given the difference between them.

IMHO, it is a best practice to inject dependencies this way wherever possible instead of doing it bootstrap, as it allows you to limit the scope of availability of a given dependency to the part of the application (i.e. component sub-tree) where you want it to be available. It's also conducive to progressive loading and avoids the SoC smell of configuring myriad unrelated injectables in a single bootstrap file.