I have an angular application and the client wants the path of the Backend in a json file, so he can change it easily whithout needing of another deployment.
Well i did it, but when i refresh the page or close the app and reopen it, the app don't detect the path of the backend, it is like a problem of retard or synchronisation.
This is the error in the console :
http://***/undefinedapi/Leave/GetlistLeave
This is how i did it :
The json file :
{
"ApiRoot": "http://***/"
}
How i read from the constant from the json file :
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs';
import { apiRoot } from '../model/model.apiRoot';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class apiRootService {
static apiRoot: string;
constructor(private http: Http) { }
public initialiseApiRoot()
{
this.http.get('./assets/apiRoot/apiRoot.json').pipe(map((response: Response) =>
<apiRoot>response.json())).subscribe(data => {
apiRootService.apiRoot = data['ApiRoot'];
})
}
}
and then i call this function in the constructor of app.component.ts like this :
this.apiRootService.initialiseApiRoot();
and change the call of the api in every servic elike this :
return this.http.get(apiRootService.apiRoot + .....
Any hlp and thanks
Well, let's suppose you're not facing a cache problem. If it isn't a cache problem, maybe it's a matter of timing.
You can try to set your apiRoot while your app is initializing (before app.component.ts is loaded). You can do that by providing an APP_INITIALIZER as described in Angular docs. If you use a factory that returns a function providing a promise, you'll delay your app initialization until your json file is loaded so you can initialize apiRoot. A factory is a useful approach because it will allow you to inject HttpClient service during initialization in the provider (you'll need it to get your json file).
You can do something like (in your app.module.ts):
...
import {APP_INITIALIZER} from '#angular/core';
...
// Angular will inject the HttpClient because you'll
// tell it that this is a dependency of this factory
// in the providers array
export function getApiRoot(http: HttpClient) {
return () => {
return this.http.get('./assets/apiRoot/apiRoot.json').pipe(
map((response: Response) => <apiRoot>response.json()),
tap((data: any) => apiRootService.apiRoot = data['ApiRoot'])
).toPromise();
};
}
...
#NgModule({
imports: [
...
HttpClientModule,
...
],
providers: [
...
{
provide: APP_INTIALIZER,
useFactory: getApiRoot,
multi: true,
deps: [HttpClient]
}
...
]
})
export class AppModule {}
because you are going with wrong approach. you are seeting url after application is initialized. Refer :- https://medium.com/voobans-tech-stories/multiple-environments-with-angular-and-docker-2512e342ab5a. this will give general idea how to achieve build once and deploy anywhere
Related
I've read every other article or post about this I can find. I cannot for the life of me figure out where I'm going wrong with this simple task. (Specifically following this example.) I must be doing something obviously stupid but I've been looking at this so long I can't see it.
I have a json file called isRecognized.json in assets/mockData. I've added the mockData directory to my webpack config file so it's included in the /dist directory. If I go to http:localhost:4200/assets/mockData/isRecognized.json I'm able to see the file, so I know it's available.
However, when I try to retrieve the file using HTTP Client, it throws a 404 no matter what I try.
EDIT: I'm using Webpack, not Angular CLI.
app.component.ts
import { MyService } from './services/my.service';
import { Component, OnInit, Renderer2 } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
/*
* Main app component that houses all views.
*/
#Component({
selector: 'app-comp',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
constructor(
private route: ActivatedRoute, private service: MyService
) {}
ngOnInit() {
this.service.isRecognized();
}
}
my.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
#Injectable()
export class MyService {
constructor(private http: HttpClient) { }
isRecognized() {
this.getJSON('isRecognized').subscribe(data => {
console.log(data);
});
}
getJSON(fileName): Observable<any> {
return this.http.get('http://localhost:4200/assets/mockData/' + fileName + '.json');
}
}
The error I get in the browser console is:
AppComponent_Host.ngfactory.js? [sm]:1 ERROR Error: [object Object]
at viewWrappedDebugError (core.js:9795)
at callWithDebugContext (core.js:15101)
at Object.debugCheckAndUpdateView [as checkAndUpdateView] (core.js:14628)
at ViewRef_.webpackJsonp../node_modules/#angular/core/esm5/core.js.ViewRef_.detectChanges (core.js:11605)
at core.js:5913
at Array.forEach (<anonymous>)
at ApplicationRef.webpackJsonp../node_modules/#angular/core/esm5/core.js.ApplicationRef.tick (core.js:5913)
at core.js:5746
at ZoneDelegate.webpackJsonp../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
at Object.onInvoke (core.js:4756)
If I debug the error, I can see the body of the error is:
I have this working successfully in my app just using a URL like this:
private productUrl = 'api/products/products.json';
Notice that it does not have the localhost part of the path.
So try something more like this:
'assets/mockData/' + fileName + '.json'
Also ensure that your angular.json has the path listed under assets:
"assets": [
"src/favicon.ico",
"src/assets",
"src/api"
],
NOTE: I also didn't do anything to modify my webpack configuration. (But I'm using the CLI.)
If you want to look at some working code that accesses a json file, I have an example here:
https://stackblitz.com/edit/github-gettingstarted-deborahk
I would suggest subscribing in the component, and returning an Observable from the service:
Service:
#Injectable()
export class MyService {
constructor(private http: HttpClient) { }
isRecognized(fileName): Observable<any> {
return this.http.get('http://localhost:4200/assets/mockData/' + fileName + '.json');
}
}
Component:
export class AppComponent implements OnInit {
constructor(
private route: ActivatedRoute, private service: MyService
) {}
ngOnInit() {
this.service.isRecognized(fileName)
.subscribe(data => {
console.log(data);
});
}
}
While this might not be a direct solution to your specific problem, you should take a look at the npm package json-server. I use it to mock the API when developing and testing the client.
json-server npm
It will run a node web server on port 3000, and is really easy to use right out of the box.
See this tutorial example of how to use it:
Mock api with json-server
There might be better examples and setting up the proxy isn't necessary, but should be good enough.
Finally figured out the answer! I had this in my app.module.ts file as an import:
InMemoryWebApiModule.forRoot(InMemoryDataService, { dataEncapsulation: false })
Removing this fixed the issue immediately.
I have searched about it in google, Yes I understood that am receiving context/type:text/html not application/json,but I didn't get how to solve this issue. when I trying to hit the api of third party user from my local server , am getting this error. Please check the screenshot of this error.
service.ts
export class VamosysService {
constructor(private _http:Http){}
getVechicalLocations():Observable<any[]>{
return this._http.get(API_URL)
.pipe(map((response:Response)=<any[]>response.json()));
}
component.ts
export class VamosysComponent implements OnInit {
vechicalLocation:any[];
constructor(private _vamoService:VamosysService) { }
ngOnInit() {
this._vamoService.getVechicalLocations()
.subscribe((data) => this.vechicalLocation = data);
}
}
Thanks in advance
You are using HttpModule which is deprecated you should use HttpClientModule instead
In new HttpClientModule JSON is an assumed default and no longer needs to be explicitly parsed using res.json()
Service
import { HttpClient } from '#angular/common/http';
export class VamosysService {
constructor(private _httpc:HttpClient){}
getVechicalLocations():Observable<any[]>{
return this._httpc.get(API_URL);}
Component
export class VamosysComponent implements OnInit {
public vechicalLocation=[];
constructor(private _vamoService:VamosysService) { }
ngOnInit() {
this._vamoService.getVechicalLocations()
.subscribe((data) => this.vechicalLocation=data);
}}
In Case if you are Requesting non-JSON data
you can use the responseType property.
getVechicalLocations():Observable<any[]>{
return this._httpc.get(API_URL,{ responseType: 'text' });}
response type could be responseType?: 'arraybuffer' | 'blob' | 'json' | 'text'
I had a similar error when getting a string back from the response. My error was:
error: {error: SyntaxError: Unexpected token W in JSON at position 0 at JSON.parse () at XMLHttp…, text: "We've just sent you an email to reset your password."}
And I don't have control over the actual response. However, I found this in the documentation which specifically sets that you can be expecting non-JSON responses. So you would have something like
return this._http.get(API_URL, {responseType: 'text'}) // Notice the additional parameter here
.pipe(map((response:Response)=<any[]>response.json()));
I notice you're also specifically parsing json after though, so it would be depending on the actual response on how you want to handle it.
According to your STACKBLITZ. The api that you have used is not working properly. If it changed to working get api call it is working properly. WORKING demo with different test api call.
and instead of using Http I'm suggesting you to use HttpClient with additional benefits.
The HttpClient in #angular/common/http offers a simplified client HTTP
API for Angular applications that rests on the XMLHttpRequest
interface exposed by browsers. Additional benefits of HttpClient
include testability features, typed request and response objects,
request and response interception, Observable apis, and streamlined
error handling.
Then you no need explicitly parsed into json inside map. And also it will return a observable type. You could just consume the return json after subscribe to it.
Before you can use the HttpClient, you need to import the Angular HttpClientModule (into root AppModule).
Sample AppModule code:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HttpClientModule } from '#angular/common/http';
#NgModule({
imports: [
BrowserModule,
// import HttpClientModule after BrowserModule.
HttpClientModule,
],
declarations: [
AppComponent,
],
bootstrap: [ AppComponent ]
})
export class AppModule {}
Then import HttpClient inside your VamosysService
import { HttpClient } from '#angular/common/http';
Try to use getVechicalLocations() like below
constructor(private _http: HttpClient) { }
getVechicalLocations(){
return this._http.get(API_URL);
}
Hope this helps to you!
I am using proxy.conf.json as I develop my Angular application.
However I would like to, for a few endpoints, simply return a JSON object when called. Currently my proxy.conf file redirects to a locally running backend which returns these JSONs. However I'd rather not run the backend server and simply return the JSON from proxy.conf.json.
Is this possible somehow?
It is possible by using proxy.conf.js instead of proxy.conf.json. Then you can specify a bypass function where you can return a response directly. This is mentioned in the angular-cli documentation for the proxy but it does not give many details. Here is a sample proxy.conf.js file to do it.
const PROXY_CONFIG = {
'/api': {
'target': 'http://localhost:5000',
'bypass': function (req, res, proxyOptions) {
switch (req.url) {
case '/api/json1':
const objectToReturn1 = {
value1: 1,
value2: 'value2',
value3: 'value3'
};
res.end(JSON.stringify(objectToReturn1));
return true;
case '/api/json2':
const objectToReturn2 = {
value1: 2,
value2: 'value3',
value3: 'value4'
};
res.end(JSON.stringify(objectToReturn2));
return true;
}
}
}
}
module.exports = PROXY_CONFIG;
You need to recheck the url in the bypass function because it is called for all /api requests and then you just directly return a response for the ones you want the others will still be redirected to the target address. You return true to tell the proxy the request finished.
Make sure to then specify the correct file when running ng serve --proxy-config proxy.conf.js.
For returning JSON from proxy.config.conf, there doesn't seem to be an easy way to do it.
One way would be to have a baseApiUrl in the environment.ts file as well urlSuffix set to .json. Then all of the API calls would have to be something like this: enviroment.baseApiUrl + uri + environment.urlSuffix. Then in environment.prod.ts, the urlSuffix would be an empty string. This is a hacky solution but would work.
Alternative using HTTP_INTERCEPTORS
A cleaner solution that leverages the framework, would be to use an HttpInterceptor with the HttpClient along with setting the baseApiUrl in the environment.ts file. This allows for different API endpoints per environment.
environment.ts
export const environment = {
apiBaseUrl: 'http://localhost:4200/assets/api',
production: false
}
development.interceptor.ts
import {Injectable} from '#angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import {environment} from '../environments/environment';
#Injectable()
export class DevelopmentInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let clonedRequest = null;
if (!environment.production) {
clonedRequest = request.clone({
url: `${request.url}.json`
});
return next.handle(clonedRequest);
}
return next.handle(clonedRequest);
}
}
This class will intercept any http request made by the HttpClient. Using the properties in the environment.ts file, you can check if the current build is a production build. If it is, clone the request and append .json to it. Anything that is in the assets folder is accessible from the browser. Below is a sample file that maps to the url http:localhost:4200/assets/api/test.json.
/src/assets/api/test.json
{
"name": "test",
"description": "Test Data"
}
Place this file in the assets directory and have the directory structure follow the endpoints of the actual API.
test.service.ts
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import {environment} from '../environments/environment';
#Injectable()
export class TestService {
private baseUrl = `${environment.apiBaseUrl}/test`;
constructor(private http: HttpClient) {
}
getTestData(): Observable<any> {
return this.http.get(this.baseUrl);
}
}
Import the environment.ts file here and set the base url to the apiBaseurl property. As long as you import the environment.ts file and not the environment.prod.ts file, this will work in all environments as the appropriate environment file will be read from when the app is built. So this url only has to be change in one place per environment.
Angular CLI Build Targets & Environment Files
app.component.ts
import {Component, OnInit} from '#angular/core';
import {TestService} from './test.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'app';
constructor(private testService: TestService) {
}
ngOnInit() {
this.testService.getTestData().subscribe(
(testData) => console.log(testData)
);
}
}
Here the TestService is injected into the AppComponent and the getTestData() method is called to fetch data from the API. The DevelopmentInterceptor checks the environment and appends .json to the request. Then the data is logged to the console.
app.module.ts
import {BrowserModule} from '#angular/platform-browser';
import {NgModule} from '#angular/core';
import {HTTP_INTERCEPTORS, HttpClientModule} from '#angular/common/http';
import {AppComponent} from './app.component';
import {DevelopmentInterceptor} from './development.interceptor';
import {TestService} from './test.service';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
TestService,
{
provide: HTTP_INTERCEPTORS,
useClass: DevelopmentInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Register the TestService and the DevelopmentInterceptor as providers.
Using this setup, proxy.config.json is not necessary.
For more information on HttpInterceptors, there is the Angular Documentation Intercepting Http Requests & Responses.
There is also a tutorial by Jason Watmore that does some more advanced things with this approach. Angular 5 - Mock Backend Example for Backendless Development
I'm developing an Angular app and I'm looking for something similar to Android Resource available in Android development.
Here is the way to get a string in Android:
String mystring = getResources().getString(R.string.mystring);
I would like to have the same in Angular.
For example if I have few HTML templates in which there are the same message about the wrong email provided...
<div class="alert alert-danger">
<strong>Error!</strong>Invalid e-mail
</div>
I would like to have the following:
<div class="alert alert-danger">
<strong>Error!</strong>{{myStrings.INVALID_EMAIL}}
</div>
...or something like this...
<div class="alert alert-danger">
<strong>Error!</strong>{{'INVALID_EMAIL' | stringGenerator}}
</div>
Do you know a way or addon I can install to reach that?
Having configuration, translations and resources separated from application's logic is very useful. Configuration would also be very helpful in other context like, for example, getting api_url useful for any rest call.
You can set up such thing using #angular/cli. Having the following application structure:
|- app
|- assets
|- i18n
- en.json
- it.json
|- json-config
- development.json
- env.json
- production.json
|- resources
- en.json
- it.json
|- environment
- environment.prod.ts
- environment.ts
|- config
- app.config.ts
Where:
app: contain all application logics
assets/i18n/*.json: contains a textual resources that can be used in any of your components. There's one of them for each language we want to cover.
E.G. en.json:
{
"TEST": {
"WELCOME" : "Welcome"
}
E.G it.json:
{
"TEST": {
"WELCOME" : "Benvenuto"
}
assets/json-config: contains configuration files to use in development mode and production mode. Also contains env.json which is a json that says which is the current development mode:
E.G. env.json:
{
"env" : "development"
}
E.G. development.json:
{
"API_URL" : "someurl",
"MYTOKEN" : "sometoken",
"DEBUGGING" : true
}
assets/resources: contains jsons files of resources per each language we want to cover. For instance, it may contain jsons initialization's for application models. It's useful if, for example, you want to fill an array of a model to be passed to an *ngFor personalized based on enviroment and/or language. Such initialization should be done inside each component which want to access a precise resource via AppConfig.getResourceByKey that will be shown later.
app.config.ts: Configuration Service that loads resources based on development mode. I will show a snippet below.
Basic Configuration:
In order to load basic configuration files as the application starts we need to do a few things.
app.module.ts:
import { NgModule, APP_INITIALIZER } from '#angular/core';
/** App Services **/
import { AppConfig } from '../config/app.config';
import { TranslationConfigModule } from './shared/modules/translation.config.module';
// Calling load to get configuration + translation
export function initResources(config: AppConfig, translate: TranslationConfigModule) {
return () => config.load(translate);
}
// Initializing Resources and Translation as soon as possible
#NgModule({
. . .
imports: [
. . .
TranslationConfigModule
],
providers: [
AppConfig, {
provide: APP_INITIALIZER,
useFactory: initResources,
deps: [AppConfig, TranslationConfigModule],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.config.ts:
As said above, this service loads configuration files based on development mode and, in this case, browser language. Loading resources based on language can be very useful if you want to customize your application. For example, your italian distribution would have different routes, different behavior or simple different texts.
Every Resources, Configuration and Enviroment entry is available trough AppConfig service's methods such as getEnvByKey, getEntryByKey and getResourceByKey.
import { Inject, Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { get } from 'lodash';
import 'rxjs/add/operator/catch';
import { TranslationConfigModule } from '../app/shared/modules/translation.config.module';
#Injectable()
export class AppConfig {
private _configurations: any = new Object();
private _config_path = './assets/json-config/';
private _resources_path = './assets/resources/';
constructor( private http: Http) { }
// Get an Environment Entry by Key
public getEnvByKey(key: any): any {
return this._configurations.env[key];
}
// Get a Configuration Entryby Key
public getEntryByKey(key: any): any {
return this._configurations.config[key];
}
// Get a Resource Entry by Key
public getResourceByKey(key: any): any {
return get(this._configurations.resource, key);
}
// Should be self-explanatory
public load(translate: TranslationConfigModule){
return new Promise((resolve, reject) => {
// Given env.json
this.loadFile(this._config_path + 'env.json').then((envData: any) => {
this._configurations.env = envData;
// Load production or development configuration file based on before
this.loadFile(this._config_path + envData.env + '.json').then((conf: any) => {
this._configurations.config = conf;
// Load resources files based on browser language
this.loadFile(this._resources_path + translate.getBrowserLang() +'.json').then((resource: any) => {
this._configurations.resource = resource;
return resolve(true);
});
});
});
});
}
private loadFile(path: string){
return new Promise((resolve, reject) => {
this.http.get(path)
.map(res => res.json())
.catch((error: any) => {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
})
.subscribe((res_data) => {
return resolve(res_data);
})
});
}
}
translation.config.module.ts
This module sets up translation built using ngx-translate. Sets up translation depending on the browser language.
import { HttpModule, Http } from '#angular/http';
import { NgModule, ModuleWithProviders } from '#angular/core';
import { TranslateModule, TranslateLoader, TranslateService } from '#ngx-translate/core';
import { TranslateHttpLoader } from '#ngx-translate/http-loader';
import { isNull, isUndefined } from 'lodash';
export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, '../../../assets/i18n/', '.json');
}
const translationOptions = {
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [Http]
}
};
#NgModule({
imports: [TranslateModule.forRoot(translationOptions)],
exports: [TranslateModule],
providers: [TranslateService]
})
export class TranslationConfigModule {
private browserLang;
/**
* #param translate {TranslateService}
*/
constructor(private translate: TranslateService) {
// Setting up Translations
translate.addLangs(['en', 'it']);
translate.setDefaultLang('en');
this.browserLang = translate.getBrowserLang();
translate.use(this.browserLang.match(/en|it/) ? this.browserLang : 'en');
}
public getBrowserLang(){
if(isUndefined(this.browserLang) || isNull(this.browserLang)){
this.browserLang = 'en';
}
return this.browserLang;
}
}
Ok, and now? How can I use such configuration?
Any Module/Component imported into app.module.ts or any of them that is imported into another custom module that is importing translation.config.module can now automatically translate any interpolated entry based on browser language. For instance using the following snipped will generate Welcome or Benvenuto based on explained behavior:
{{ 'TEST.WELCOME' | translate }}
What If I want to get a resource to initialize a certain array that will be passed to an *ngFor?
In any component, just do that inside the constructor:
. . .
// Just some model
public navigationLinks: NavigationLinkModel[];
constructor(private _config: AppConfig) {
// PAGES.HOMEPAGE.SIDENAV.NAVIGATION contains such model data
this.navigationLinks =
this._config.getResourceByKey('PAGES.HOMEPAGE.SIDENAV.NAVIGATION');
}
Of course you can also combinate resources and configuration.
AndreaM16's answer is definitely thorough, but you sould also consider using an existing package for this instead of writing your own custom code that you need to maintain. To that end, I would recommend checking out ngx-translate (which is used somewhat under the hood in that answer), transloco and Angular's built-in i18n capabilities.
Most of those approaches are based on the magic strings, where your HTML templates and typescript code need to contain strings that (hopefully) match the keys in a JSON or XML file. This presents a serious code maintenance issue which won't be revealed at compile time, only at runtime. I would also advocate for checking out some guides on creating a type-safe translation system:
https://medium.com/angular-in-depth/angular-typed-translations-29353f0a60bc
https://medium.com/angular-in-depth/dynamic-import-of-locales-in-angular-b994d3c07197
You can also use enum file for string values.
string.ts
export enum TEST_STRING {
TEST_ONE = "this is first test string",
TEST_TWO = "this is second test string",
TEST_THREE = "this is third test string"
}
component.ts
import { TEST_STRING } from '../resources/string';
var testString = TEST_STRING.TEST_ONE;
console.log(testString); // result = this is first test string
Is there a way to pass arguments rendered on the backend to angular2 bootstrap method? I want to set http header for all requests using BaseRequestOptions with value provided from the backend. My main.ts file looks like this:
import { bootstrap } from '#angular/platform-browser-dynamic';
import { AppComponent } from "./app.component.ts";
bootstrap(AppComponent);
I found how to pass this arguments to root component (https://stackoverflow.com/a/35553650/3455681), but i need it when I'm fireing bootstrap method... Any ideas?
edit:
webpack.config.js content:
module.exports = {
entry: {
app: "./Scripts/app/main.ts"
},
output: {
filename: "./Scripts/build/[name].js"
},
resolve: {
extensions: ["", ".ts", ".js"]
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader'
}
]
}
};
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})]);
In Angular2 final release, the APP_INITIALIZER provider can be used to achieve what you want.
I wrote a Gist with a complete example: https://gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9
The gist example is reading from JSON files but can be easily changed to read from a REST endpoint.
What you need, is basically:
a) Set up APP_INITIALIZER in your existent module file:
import { APP_INITIALIZER } from '#angular/core';
import { BackendRequestClass } from './backend.request';
import { HttpModule } from '#angular/http';
...
#NgModule({
imports: [
...
HttpModule
],
...
providers: [
...
...
BackendRequestClass,
{ provide: APP_INITIALIZER, useFactory: (config: BackendRequestClass) => () => config.load(), deps: [BackendRequestClass], multi: true }
],
...
});
These lines will call the load() method from BackendRequestClass class before your application is started.
Make sure you set "HttpModule" in "imports" section if you want to make http calls to the backend using angular2 built in library.
b) Create a class and name the file "backend.request.ts":
import { Inject, Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class BackendRequestClass {
private result: Object = null;
constructor(private http: Http) {
}
public getResult() {
return this.result;
}
public load() {
return new Promise((resolve, reject) => {
this.http.get('http://address/of/your/backend/endpoint').map( res => res.json() ).catch((error: any):any => {
reject(false);
return Observable.throw(error.json().error || 'Server error');
}).subscribe( (callResult) => {
this.result = callResult;
resolve(true);
});
});
}
}
c) To read the contents of the backend call, you just need to inject the BackendRequestClass into any class of you choice and call getResult(). Example:
import { BackendRequestClass } from './backend.request';
export class AnyClass {
constructor(private backendRequest: BackendRequestClass) {
// note that BackendRequestClass is injected into a private property of AnyClass
}
anyMethod() {
this.backendRequest.getResult(); // This should return the data you want
}
}
Let me know if this solves your problem.
Instead of having your entry point calling bootstrap itself, you could create and export a function that does the work:
export function doBootstrap(data: any) {
platformBrowserDynamic([{provide: Params, useValue: new Params(data)}])
.bootstrapModule(AppModule)
.catch(err => console.error(err));
}
You could also place this function on the global object, depending on your setup (webpack/SystemJS). It also is AOT-compatible.
This has the added benefit to delay the bootstrap, whenit makes sense. For instance, when you retrieve this user data as an AJAX call after the user fills out a form. Just call the exported bootstrap function with this data.
The only way to do that is to provide these values when defining your providers:
bootstrap(AppComponent, [
provide(RequestOptions, { useFactory: () => {
return new CustomRequestOptions(/* parameters here */);
});
]);
Then you can use these parameters in your CustomRequestOptions class:
export class AppRequestOptions extends BaseRequestOptions {
constructor(parameters) {
this.parameters = parameters;
}
}
If you get these parameters from an AJAX request, you need to bootstrap asynchronously this way:
var appProviders = [ HTTP_PROVIDERS ]
var app = platform(BROWSER_PROVIDERS)
.application([BROWSER_APP_PROVIDERS, appProviders]);
var http = app.injector.get(Http);
http.get('http://.../some path').flatMap((parameters) => {
return app.bootstrap(appComponentType, [
provide(RequestOptions, { useFactory: () => {
return new CustomRequestOptions(/* parameters here */);
}})
]);
}).toPromise();
See this question:
angular2 bootstrap with data from ajax call(s)
Edit
Since you have your data in the HTML you could use the following.
You can import a function and call it with parameters.
Here is a sample of the main module that bootstraps your application:
import {bootstrap} from '...';
import {provide} from '...';
import {AppComponent} from '...';
export function main(params) {
bootstrap(AppComponent, [
provide(RequestOptions, { useFactory: () => {
return new CustomRequestOptions(params);
});
]);
}
Then you can import it from your HTML main page like this:
<script>
var params = {"token": "#User.Token", "xxx": "#User.Yyy"};
System.import('app/main').then((module) => {
module.main(params);
});
</script>
See this question: Pass Constant Values to Angular from _layout.cshtml.