Reputation: 3243
I want to bootstrap my application with data I am retrieving from a service. I am doing something along the lines of
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)
});
This works fine, but I do not like using the dependency array twice, is there a cleaner way of doing this? Can I add things to the application injector after bootstrap? Also I notice that the bootstrap function returns a promise, can I use this promise to prevent bootstrap of the application until after my ajax request finishes?
Of course for the Injector
I could use only those dependencies required by MyService
but this makes it very brittle as you can imagine.
Upvotes: 5
Views: 4586
Reputation: 1308
You can also use the APP_INITIALIZER
injection token and you can also have it call multiple async resources in parallel:
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 { }
Source: Angular 4 Tutorial – Run Code During App Initialization
Another interesting article: Hook into Angular’s Initialization Process
Upvotes: 0
Reputation: 202146
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.
Upvotes: 5
Reputation: 32670
@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.
Upvotes: 1