How to Pass Parameters Rendered from Backend to Angular2 Bootstrap Method

How to pass parameters rendered from backend to angular2 bootstrap method

update2

Plunker example

update AoT

To work with AoT the factory closure needs to be moved out

function loadContext(context: ContextService) {
return () => context.load();
}

@NgModule({
...
providers: [ ..., ContextService, { provide: APP_INITIALIZER, useFactory: loadContext, deps: [ContextService], multi: true } ],

See also https://github.com/angular/angular/issues/11262

update an RC.6 and 2.0.0 final example

function configServiceFactory (config: ConfigService) {
return () => config.load();
}

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule,
routes,
FormsModule,
HttpModule],
providers: [AuthService,
Title,
appRoutingProviders,
ConfigService,
{ provide: APP_INITIALIZER,
useFactory: configServiceFactory
deps: [ConfigService],
multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }

If there is no need to wait for the initialization to complete, the constructor of `class AppModule {} can also be used:

class AppModule {
constructor(/*inject required dependencies */) {...}
}

hint (cyclic dependency)

For example injecting the router can cause cyclic dependencies.
To work around, inject the Injector and get the dependency by

this.myDep = injector.get(MyDependency);

instead of injecting MyDependency directly like:

@Injectable()
export class ConfigService {
private router:Router;
constructor(/*private router:Router*/ injector:Injector) {
setTimeout(() => this.router = injector.get(Router));
}
}

update

This should work the same in RC.5 but instead add the provider to providers: [...] of the root module instead of bootstrap(...)

(not tested myself yet).

update

An interesting approach to do it entirely inside Angular is explained here https://github.com/angular/angular/issues/9047#issuecomment-224075188

You can use APP_INITIALIZER which will execute a function when the
app is initialized and delay what it provides if the function returns
a promise. This means the app can be initializing without quite so
much latency and you can also use the existing services and framework
features.

As an example, suppose you have a multi-tenanted solution where the
site info relies on the domain name it's being served from. This can
be [name].letterpress.com or a custom domain which is matched on the
full hostname. We can hide the fact that this is behind a promise by
using APP_INITIALIZER.

In bootstrap:

{provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps:[SitesService, HTTP_PROVIDERS], multi: true}),

sites.service.ts:

@Injectable()
export class SitesService {
public current:Site;

constructor(private http:Http, private config:Config) { }

load():Promise<Site> {
var url:string;
var pos = location.hostname.lastIndexOf(this.config.rootDomain);
var url = (pos === -1)
? this.config.apiEndpoint + '/sites?host=' + location.hostname
: this.config.apiEndpoint + '/sites/' + location.hostname.substr(0, pos);
var promise = this.http.get(url).map(res => res.json()).toPromise();
promise.then(site => this.current = site);
return promise;
}

NOTE: config is just a custom config class. rootDomain would be
'.letterpress.com' for this example and would allow things like
aptaincodeman.letterpress.com.

Any components and other services can now have Site injected into
them and use the .current property which will be a concrete
populated object with no need to wait on any promise within the app.

This approach seemed to cut the startup latency which was otherwise
quite noticeable if you were waiting for the large Angular bundle to
load and then another http request before the bootstrap even began.

original

You can pass it using Angulars dependency injection:

var headers = ... // get the headers from the server

bootstrap(AppComponent, [{provide: 'headers', useValue: headers})]);
class SomeComponentOrService {
constructor(@Inject('headers') private headers) {}
}

or provide prepared BaseRequestOptions directly like

class MyRequestOptions extends BaseRequestOptions {
constructor (private headers) {
super();
}
}

var values = ... // get the headers from the server
var headers = new MyRequestOptions(values);

bootstrap(AppComponent, [{provide: BaseRequestOptions, useValue: headers})]);

Send http request before sending other http requests angular 2

this.http.get(...)
.map(response => response.json())
.flatMap(response => this.http.get(...).map(response => response.json())
.subscribe(response => this.result = response);

You can use How to pass parameters rendered from backend to angular2 bootstrap method to delay app-initialization.

How to pass parameters rendered from backend to angular2 bootstrap method

update2

Plunker example

update AoT

To work with AoT the factory closure needs to be moved out

function loadContext(context: ContextService) {
return () => context.load();
}

@NgModule({
...
providers: [ ..., ContextService, { provide: APP_INITIALIZER, useFactory: loadContext, deps: [ContextService], multi: true } ],

See also https://github.com/angular/angular/issues/11262

update an RC.6 and 2.0.0 final example

function configServiceFactory (config: ConfigService) {
return () => config.load();
}

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule,
routes,
FormsModule,
HttpModule],
providers: [AuthService,
Title,
appRoutingProviders,
ConfigService,
{ provide: APP_INITIALIZER,
useFactory: configServiceFactory
deps: [ConfigService],
multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }

If there is no need to wait for the initialization to complete, the constructor of `class AppModule {} can also be used:

class AppModule {
constructor(/*inject required dependencies */) {...}
}

hint (cyclic dependency)

For example injecting the router can cause cyclic dependencies.
To work around, inject the Injector and get the dependency by

this.myDep = injector.get(MyDependency);

instead of injecting MyDependency directly like:

@Injectable()
export class ConfigService {
private router:Router;
constructor(/*private router:Router*/ injector:Injector) {
setTimeout(() => this.router = injector.get(Router));
}
}

update

This should work the same in RC.5 but instead add the provider to providers: [...] of the root module instead of bootstrap(...)

(not tested myself yet).

update

An interesting approach to do it entirely inside Angular is explained here https://github.com/angular/angular/issues/9047#issuecomment-224075188

You can use APP_INITIALIZER which will execute a function when the
app is initialized and delay what it provides if the function returns
a promise. This means the app can be initializing without quite so
much latency and you can also use the existing services and framework
features.

As an example, suppose you have a multi-tenanted solution where the
site info relies on the domain name it's being served from. This can
be [name].letterpress.com or a custom domain which is matched on the
full hostname. We can hide the fact that this is behind a promise by
using APP_INITIALIZER.

In bootstrap:

{provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps:[SitesService, HTTP_PROVIDERS], multi: true}),

sites.service.ts:

@Injectable()
export class SitesService {
public current:Site;

constructor(private http:Http, private config:Config) { }

load():Promise<Site> {
var url:string;
var pos = location.hostname.lastIndexOf(this.config.rootDomain);
var url = (pos === -1)
? this.config.apiEndpoint + '/sites?host=' + location.hostname
: this.config.apiEndpoint + '/sites/' + location.hostname.substr(0, pos);
var promise = this.http.get(url).map(res => res.json()).toPromise();
promise.then(site => this.current = site);
return promise;
}

NOTE: config is just a custom config class. rootDomain would be
'.letterpress.com' for this example and would allow things like
aptaincodeman.letterpress.com.

Any components and other services can now have Site injected into
them and use the .current property which will be a concrete
populated object with no need to wait on any promise within the app.

This approach seemed to cut the startup latency which was otherwise
quite noticeable if you were waiting for the large Angular bundle to
load and then another http request before the bootstrap even began.

original

You can pass it using Angulars dependency injection:

var headers = ... // get the headers from the server

bootstrap(AppComponent, [{provide: 'headers', useValue: headers})]);
class SomeComponentOrService {
constructor(@Inject('headers') private headers) {}
}

or provide prepared BaseRequestOptions directly like

class MyRequestOptions extends BaseRequestOptions {
constructor (private headers) {
super();
}
}

var values = ... // get the headers from the server
var headers = new MyRequestOptions(values);

bootstrap(AppComponent, [{provide: BaseRequestOptions, useValue: headers})]);

How to pass parameters rendered from backend to angular2 bootstrap method

update2

Plunker example

update AoT

To work with AoT the factory closure needs to be moved out

function loadContext(context: ContextService) {
return () => context.load();
}

@NgModule({
...
providers: [ ..., ContextService, { provide: APP_INITIALIZER, useFactory: loadContext, deps: [ContextService], multi: true } ],

See also https://github.com/angular/angular/issues/11262

update an RC.6 and 2.0.0 final example

function configServiceFactory (config: ConfigService) {
return () => config.load();
}

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule,
routes,
FormsModule,
HttpModule],
providers: [AuthService,
Title,
appRoutingProviders,
ConfigService,
{ provide: APP_INITIALIZER,
useFactory: configServiceFactory
deps: [ConfigService],
multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }

If there is no need to wait for the initialization to complete, the constructor of `class AppModule {} can also be used:

class AppModule {
constructor(/*inject required dependencies */) {...}
}

hint (cyclic dependency)

For example injecting the router can cause cyclic dependencies.
To work around, inject the Injector and get the dependency by

this.myDep = injector.get(MyDependency);

instead of injecting MyDependency directly like:

@Injectable()
export class ConfigService {
private router:Router;
constructor(/*private router:Router*/ injector:Injector) {
setTimeout(() => this.router = injector.get(Router));
}
}

update

This should work the same in RC.5 but instead add the provider to providers: [...] of the root module instead of bootstrap(...)

(not tested myself yet).

update

An interesting approach to do it entirely inside Angular is explained here https://github.com/angular/angular/issues/9047#issuecomment-224075188

You can use APP_INITIALIZER which will execute a function when the
app is initialized and delay what it provides if the function returns
a promise. This means the app can be initializing without quite so
much latency and you can also use the existing services and framework
features.

As an example, suppose you have a multi-tenanted solution where the
site info relies on the domain name it's being served from. This can
be [name].letterpress.com or a custom domain which is matched on the
full hostname. We can hide the fact that this is behind a promise by
using APP_INITIALIZER.

In bootstrap:

{provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps:[SitesService, HTTP_PROVIDERS], multi: true}),

sites.service.ts:

@Injectable()
export class SitesService {
public current:Site;

constructor(private http:Http, private config:Config) { }

load():Promise<Site> {
var url:string;
var pos = location.hostname.lastIndexOf(this.config.rootDomain);
var url = (pos === -1)
? this.config.apiEndpoint + '/sites?host=' + location.hostname
: this.config.apiEndpoint + '/sites/' + location.hostname.substr(0, pos);
var promise = this.http.get(url).map(res => res.json()).toPromise();
promise.then(site => this.current = site);
return promise;
}

NOTE: config is just a custom config class. rootDomain would be
'.letterpress.com' for this example and would allow things like
aptaincodeman.letterpress.com.

Any components and other services can now have Site injected into
them and use the .current property which will be a concrete
populated object with no need to wait on any promise within the app.

This approach seemed to cut the startup latency which was otherwise
quite noticeable if you were waiting for the large Angular bundle to
load and then another http request before the bootstrap even began.

original

You can pass it using Angulars dependency injection:

var headers = ... // get the headers from the server

bootstrap(AppComponent, [{provide: 'headers', useValue: headers})]);
class SomeComponentOrService {
constructor(@Inject('headers') private headers) {}
}

or provide prepared BaseRequestOptions directly like

class MyRequestOptions extends BaseRequestOptions {
constructor (private headers) {
super();
}
}

var values = ... // get the headers from the server
var headers = new MyRequestOptions(values);

bootstrap(AppComponent, [{provide: BaseRequestOptions, useValue: headers})]);

How to serialize initialization of angular2 components?

You can not control the lifecycle callbacks.

You can use

  • *ngIf to not show parts of the application until the condition changes to true. If you wrap the content of the root component template with such an *ngIf="isLoaded" no component except the root component will be created.

  • Route guards to prevent route navigation until a condition becomes true.

  • APP_INITIALIZER that prevents initialization of your Angular2 application until the returned Promise resolves. See How to pass parameters rendered from backend to angular2 bootstrap method for an example

  • Observables to notify about state changes and also to cache responses to prevent multiple calls. See What is the correct way to share the result of an Angular 2 Http network call in RxJs 5? for an example.

The chosen way for my problem was the usage of the APP_INITIALIZER. Services utilizing the router (such as Angulartics2) can't be used, because at this point the router hasn't been initialized yet.
Below is an example code using async-await.

@NgModule({
imports: [
HttpModule,
routing,
SharedModule.forRoot()
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'},
{provide: APP_INITIALIZER,
useFactory: (appModule:AppModule) => async () => {
await appModule.init();
}, deps: [AppModule], multi:true}
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {

constructor(private service:Service) {
}

// tslint:disable-next-line:typedef
async init() {
await this.service.init();
}
}


Related Topics



Leave a reply



Submit