Angular 2 does a lot of awesome work for you. But it doesn’t do everything you might expect. One thing it doesn’t do is set a lot of HTTP request headers.
Now that makes sense as Angular doesn’t know what you are doing with a request so you really need to do so. But most Http request made with the Http service are going to be for JSON serialized data. The default request however add no headers to let the server know this. The result is that different browsers will do very different requests.
This is an example of an Http request made by Chrome:
GET http://localhost:4200/movies.json HTTP/1.1
Host: localhost:4200
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Accept: */*
Referer: http://localhost:4200/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8,nl;q=0.6
If-None-Match: W/"3a4c7-1590757b458"
If-Modified-Since: Fri, 16 Dec 2016 11:15:05 GMT
And this is the same request made with FireFox:
GET http://localhost:4200/movies.json HTTP/1.1
Host: localhost:4200
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:4200/
Connection: keep-alive
If-Modified-Since: Fri, 16 Dec 2016 11:15:05 GMT
If-None-Match: W/"3a4c7-1590757b458"
Cache-Control: max-age=0
Notice the difference between the two requests? And specially the Accept header where Chrome will claim to accept anything and FireFox indicates a preference for HTML or XML.
Adding HTTP headers
Setting HTTP headers for a request is not hard. When calling the Http.get() function you can specify the headers and you are all good.
@Injectable()
export class MoviesService {
constructor(private http: Http) { }
getMovies(): Observable<Movie[]> {
var options = new RequestOptions({
headers: new Headers({
'Accept': 'application/json'
})
});
return this.http
.get('/movies.json', options)
.map(resp => resp.json());
}
}
However doing this in every service that does an HTTP request is rather tedious and easy to forget so there must be an easier way.
BaseRequestOptions and dependency injection to the rescue
As it happens Angular use the BaseRequestOptions type as the default for all options. So if we can just change this default we are good to go. And that is exactly what dependency injection will let us do.
First we need to define our own default request options class with whatever settings you would like. In this case I am just adding two headers.
@Injectable()
export class DefaultRequestOptions extends BaseRequestOptions {
headers = new Headers({
'Accept': 'application/json',
'X-Requested-By':'Angular 2',
});
}
Next we configure the DI provider to use out class instead of the default.
@NgModule({
// Other settings
providers: [
MoviesService,
{provide: RequestOptions, useClass: DefaultRequestOptions }
],
})
export class AppModule { }
And we are good to go with every Http request using our default two headers. Here the example from Chrome.
GET http://localhost:4200/movies.json HTTP/1.1
Host: localhost:4200
Connection: keep-alive
Cache-Control: max-age=0
Accept: application/json
X-Requested-By: Angular 2
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Referer: http://localhost:4200/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8,nl;q=0.6
If-None-Match: W/"3a4c7-1590757b458"
If-Modified-Since: Fri, 16 Dec 2016 11:15:05 GMT
Adding dynamic Http headers
These headers are great but there is one limitation. These headers are always the same. And with some headers, for example authentication you might want to control the actual values at request time. Turns out his isn’t very hard either. Each requests merges the options, and thus headers, from the current request with the default request options using the merge function. As we can override this we can add whatever dynamic header we want.
@Injectable()
export class DefaultRequestOptions extends BaseRequestOptions {
headers = new Headers({
'Accept': 'application/json',
'X-Requested-By':'Angular 2',
});
merge(options?: RequestOptionsArgs): RequestOptions {
var newOptions = super.merge(options);
newOptions.headers.set('X-Requested-At', new Date().toISOString());
return newOptions;
}
}
Sweet