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.
Related
I'm trying to make angular 2 service with config file passed through constructor so i can read the config after it initializes and start other part of the code. I'm trying to make config file in app.module.ts and trough provider use factory to pass it down to my service but i have no luck, I've been stuck searching for google answers but i can't find the right solution. My code is bellow, i created config
app.module.ts
const config = new AuthServiceConfig([
{
id: FacebookLoginProvider.PROVIDER_ID,
provider: new FacebookLoginProvider(),
},
{
id: LinedinLoginProvider.PROVIDER_ID,
provider: new LinedinLoginProvider(),
},
]);
export function provideConfig() {
return config;
}
export function configFactory(config: AuthServiceConfig) {
return config;
}
providers: [
...
{ provide: AuthServiceConfig, useFactory: provideConfig },
{ provide: AuthProvider, useFactory: config },
],
auth.ts
export interface AuthServiceConfigItem {
id: string;
provider: Provider;
}
export class AuthServiceConfig {
providers: Map<string, Provider> = new Map<string, Provider>();
constructor(providers: AuthServiceConfigItem[]) {
for (let i = 0; i < providers.length; i++) {
let element = providers[i];
this.providers.set(element.id, element.provider);
}
}
}
#Injectable()
export class AuthProvider {
private static readonly ERR_LOGIN_PROVIDER_NOT_FOUND =
'Login provider not found';
providers: Map<string, Provider> = new Map<string, Provider>();
constructor(config) {
this.providers = config.providers;
console.log('Ovo su providersi u konstruktoru', this.providers);
}
I haven't tried dynamically change providers, but I used the Angular CLI environment files. Based on some flag from the environment file I would instantiate the proper service in the factory function.
Take a look at the app.module.ts and product.factory.ts here: https://github.com/Farata/angulartypescript/tree/master/code-samples/Angular6/chapter5/di-samples/src/app/factory
I want to use service in global interceptor.
my code look like this :
import { VariablesService } from '../app/modules/variables/variables.service';
#Interceptor()
export class globalInterceptor implements NestInterceptor {
constructor(private service: VariablesService) {
console.log('contructor running', service); //getting null here
}
and on server.ts first i was initializing like this :
app.useGlobalInterceptors(new globalInterceptor())
but after the injection of service i have to do some modification because parameters are needed now in globalInterceptor()
const variableService = await app.get<VariablesService>(VariablesService);
app.useGlobalInterceptors(new globalInterceptor(variableService));
Now what the problem is I am getting service is null and I am unable to create the object of the service.
GitHub issue link
You can register a global interceptor directly from the module definition:
import { Module } from '#nestjs/common';
import { APP_INTERCEPTOR } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: GlobalInterceptor,
},
],
})
export class ApplicationModule {}
This is listed in the official documentation, here.
After that you must import VariablesService module in your current interceptor module for dipendancy injection
constructor(#Inject(VariablesService) private service: VariablesService) {
console.log('contructor running', service); //
}
I am attempting to write a helper function in an Angular test leveraging HttpTestingController. The reason being is because I will eventually have a series of endpoints within my Angular service, RequestService, that I want to test within this testing file. I do not want to repeatedly inject my RequestService and HttpTestingController instances into each test function that tests the service. That is redundant. Rather, I would prefer to have a single test function that takes the injected RequestService and HttpTestingController instances and repeatedly passes them into the helper function I have created, requestHelper. This way when I want to test additional endpoints, all I need to do is make a call to the helper function and provide the parameters that are needed.
The problem I am bumping into is that when the helper function runs, the service's instance for some reason does not appear to exist, Even though the test is able to access the service's functions. When it reaches my service method's call to the http.get within the callEndpoint function, it gives me the below error:
Failed: Cannot read property 'http' of undefined
This does not make sense to me because the this keyword is referring to the instance of the Angular service, and the test case is able to reach the service's function, so how could this possibly be undefined?
Here is my test spec:
import { TestBed, async, inject } from '#angular/core/testing';
import { HttpClientModule, HttpRequest, HttpParams } from '#angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { RequestService } from './request.service';
describe(`RequestService`, () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
HttpClientTestingModule
],
providers: [
RequestService
]
});
}));
afterEach(async(inject([HttpTestingController], (backend: HttpTestingController) => {
backend.verify();
})));
it(`TESTING INJECTION`, async(inject([RequestService, HttpTestingController],
(service: RequestService, backend: HttpTestingController) => {
requestHelper(service.callEndpoint,'https://endpointurl.com',backend);
})));
function requestHelper(serviceCall: Function, url: string, backendInstance: any) {
serviceCall(...serviceParams).subscribe();
backendInstance.expectOne((req: HttpRequest<any>) => {
return req.url === url
&& req.method === 'GET';
}, 'GET');
}
});
And the respective service that the spec is testing
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class RequestService {
private requestOptions = {
headers: new HttpHeaders({'Locale': 'en_US'})
};
constructor(private http: HttpClient) { }
callEndpoint(state: string, countryCode: string): Observable<Object> {
return this.http.get(`https://endpointurl.com`,this.requestOptions);
}
}
Thank you for your help!
You can bind the context in the it block:
it(`TESTING INJECTION`, async(inject([RequestService, HttpTestingController],
(service: RequestService, backend: HttpTestingController) => {
requestHelper(service.callEndpoint.bind(service),'https://endpointurl.com',backend);
})));
I have a simple code which invokes a real HTTP request :
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{person?.id}}</h2>
</div>
`,
})
export class App {
name:string;
constructor(public http: Http) {
this.http.get('https://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json()).subscribe(res => {
this.person = res;
},()=>{},()=>console.log('complete'));
}
}
But now I want to mock the request so that it will fetch data from a file containing :
export arrFakeData:any = {id:1};
I don't want to use a service . I want to mock the request.
Some examples shows to use XHRBackend and some shows how to extend the HTTP class, but they doesn't say how can I force the data to retrieve
I know that I should use
providers:[ /*{ provide: XHRBackend, useClass: MockBackend }*/]
But I don't know how.
Question:
How can I mock http request and return (for GET) the array from arrFakeData ?
PLUNKER
Personally, I would replace the this.http.get method call with an Observable.of so that you can continue programming against the same interface (Observable) without impacting the development of your components.
However, if you really want to do this then you will have to create a service that attaches a listener to all the incoming requests and returns an appropriate mock response using the tools provided by the #angular/http/testing module.
The service will look something as such:
import {Injectable} from "#angular/core";
import {MockBackend, MockConnection} from "#angular/http/testing";
import {arrFakeData} from "./fakeData";
import {ResponseOptions, Response} from "#angular/http";
#Injectable()
export class MockBackendService {
constructor(
private backend: MockBackend
) {}
start(): void {
this.backend.connections.subscribe((c: MockConnection) => {
const URL = "https://jsonplaceholder.typicode.com/posts/1";
if (c.request.url === URL) { // You can also check the method
c.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(arrFakeData)
})));
}
});
}
}
Once you have done this, you need to register all the services and make sure that the Http module is using the MockBackend instead of the XHRBackend.
#NgModule({
imports: [BrowserModule, HttpModule,],
declarations: [App],
providers: [
MockBackend,
MockBackendService,
BaseRequestOptions,
{
provide: Http,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
}
}
],
bootstrap: [App]
})
export class AppModule {
}
Last but not least, you have to actually invoke the start method, which will make sure that you will actually receive the mock data from the MockBackend. In your AppComponent you can do the following.
constructor(public http: Http, public mockBackendService: MockBackendService) {
this.mockBackendService.start();
this.http.get('https://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json())
.subscribe(res => {
this.person = res;
});
}
I hope this helps! See the plunker for the full example. https://plnkr.co/edit/h111to5PxbI97FIyKGJZ?p=preview
You can just make the http endpoint a JSON file containing whatever data you need. This is exactly how we did it on my last project for Google, and how I do it in my own side projects. We didn't bother mocking up http services and so on, we just pointed at a json file and left everything else the same.
Below error shown when I ran ng test command.
Here is my service spec,
describe('BackendService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions,
BackendService
]
});
});
it('should ...', inject([BackendService, MockBackend], (service: BackendService) => {
expect(service).toBeTruthy();
})
);
});
BackendService.ts looks like,
export class BackendService {
private baseUrl: string = 'https://foo-backend.appspot.com/_ah/api/default/v1';
constructor(private http: Http, baseName: string) {
this.baseUrl = this.baseUrl + baseName;
}
.....
}
It seems like extra parameter inside the BackendService class's constructor causes this problem..
How do you expect Angular to know what baseName is supposed to be? All constructor parameters need to be obtained from the Injector. And if there is no corresponding token for the parameter, then it can't be looked up.
You can add a token by doing
// somewhere in some file
import { OpaqueToken } from '#angular/core';
export const BASE_NAME_TOKEN = new OpaqueToken("app.base_name");
// in test class
import { BASE_NAME_TOKEN } from 'where-ever'
TestBed.configureTestingModule({
providers: [
BackendService,
{ provide: BASE_NAME_TOKEN, useValue: 'whatever-the-base-is' }
]
});
// in service constructor
import { Inject } from '#angular/core'
import { BASE_NAME_TOKEN } from 'where-ever'
constructor(http: Http, #Inject(BASE_NAME_TOKEN) baseName: string) {}
See Also:
Dependency Injection Tokens
What is the difference between #Inject vs constructor injection as normal parameter in Angular 2?