Angular: Http vs fetch api
Like any tool you encounter during development each tool will have advantages and disadvantages and it's good to think about why a tool is being used.
When we take a look at HttpClient
it originally simplified the mess that was XMLHttpRequest
. In the same way $.ajax
originally did it, HttpClient
was the 'angular solution'. It followed the ideology of everything being an observable which has advantages (you can mix and match it with other observables) and disadvantages (it adds a lot of bloat).
Advantages of HttpClient
- It allows easy mixing and matching of two observables (e.g. let's say you have one observable which returns multiple times and an API request which returns once and you want to zip the two together it's trivially easy to do). Of course, turning a promise into an observable only requires importing
from
fromrxjs
- If you forget adding an abstraction - having all requests going through an
apiService
layer - you can still use an interceptor to achieve similar results magically. - It will increase the learning curve for new Angular developers, thus making your job more special.
HttpClient
does some magic for you such as automatic retrying of requests.- It's already included in Angular, so if you need to support 7 year old browsers like IE11 you don't need to load a polyfill like with
fetch
.
Advantages of fetch
Important : All fetch related code assumes you do create a very simple abstraction (e.g. apiService
in these examples). This is the same as setting up an interceptor in HttpClient
-land.
It's the new industry standard. Once you know it, it can be used anywhere (most likely once Angular and React die - which at some point they will -
fetch
will still most likely be around).It simplifies working with service workers as the
Request
andResponse
objects are the same you are using in your normal code.HTTP requests will typically return once and only once (of course you might be loading a file chunk by chunk, but that's a very rare exception to the rule).
fetch
is built around the norm (single return values), not the exception (multiple return values), and thus returns aPromise
rather than a stream-like-type. The advantage this results in is that it plays nicely with any and all relevant new language features such asasync
andawait
. Compare:try { const posts = await this.apiService.get('/posts'); // work with posts } catch (error) { // handle error } console.log('this happens **after** the request completes');
with
this.http.get('/posts') .subscribe(posts => { // work with posts }) .catch(error => { // work with error }); console.log('this happens **before** the request completes');
(of course you can also
toPromise
each Observable that will complete (or add.pipe(take(1))
, but that's frankly a bunch of superfluous code (which I still often end up using))It simplifies onboarding of new people. When you see a request such as
this.apiService.get('/posts');
a developer from any framework can come and right-click on
.get
and check out the function definition where things such as a domain and an authentication header being added will be clearly defined.On the other hand when a developer sees
this.http.get('/posts')
they have no way of easily discovering if and where the request might be changed unless they are aware of Angular specific magic. This is one of the reasons why Angular is considered to have a steep learning curve.
There is no risk of there being magic you aren't aware of such as automatic retrying of requests which can end up in the same request triggering 4 times on the server and you having no idea how that's possible.
It's already included in the browser - provided you don't need to support 7 year old browsers - so it can result in a slightly smaller bundle size.
Complete tie
- I sincerely don't see how types are a difference, as typing a return value from any method can be done.
<Model>this.apiService.get('/posts')
works perfectly fine.
Conclusion
Personally, I would strongly recommend anybody to use fetch
with an abstraction layer. It results in easier to read code (even a junior who hasn't ever seen async
and await
is able to read it) and even if you are in a rare situation where your apiService
has to return multiple times you are still completely free to do so as you're fully in control. And in general, you should only not use the standard (fetch
) if the alternative offers significant advantages. Even if it was a perfect tie in terms of advantages and disadvantages it probably isn't worth going for a 'framework specific' solution.
HttpClient
just doesn't seem to offer any tangible advantages beyond saving a couple of minutes of time during the initial project setup where you don't need to set up an abstraction layer for API requests.
So, after 2 years of using Angular, here are some things i found out:
- There is this library called RxJS - https://rxjs-dev.firebaseapp.com/guide/overview, which is written in TypeScript (superset of JavaScript). It is like one of thee best libraries for JavaScript.
Basically, it makes event-driven apps a breeze in JS by using the Observable
paradigm. A Promise
can only return a value once and is async only. Observables can be used to return a single value OR multiple values and can be sync or async (depending on how you use it), you can add pipes and operators to transform the results before it is used/consumed by the subscriber to it and etc; it gives a functional programming feel to using it, which is great. It is far more feature-rich than promises.
A subscriber
listens or "subscribes" to and Observable (the .subscribe()
call on an observable). An Observable emits 3 types of events: next
, error
, and complete
. Next means an event with/without data was emitted, error means there was an error within the event stream, and complete means the event stream has ended and the observable will not emit anymore.
Now, just because there was an error emitted, does not mean the event stream stopped. An event stream is only stopped when the complete event happens. Also, a subscriber can unsuscribe
from an observable, meaning it will stop listening to the event stream.
- The
HttpClient
service from Angular is built using RxJS. They basically wrapped it over the old XmlHttpRequest way of making requests - https://github.com/angular/angular/blob/main/packages/common/http/src/xhr.ts#L193.
When you make a request using Angular's HttpClient service, it automatically completes the observable on ok
response, so there is no needed to call unsuscribe on the observable since it's done anyway; it won't call complete on error response but http calls only response once.
- Observables don't execute until they are subscribed to; a Promise executes immediately.
I would say Observables are far better to use over Promises; i don't see RxJS leaving any time soon or ever.
I wanted to leave this a comment but I'm too new here. For the abstraction you could use HttpBackend for the fetch call and just extend the HttpClient.
import { HttpBackend, HttpClient, HttpEvent, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, Observer } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class FetchBackend implements HttpBackend {
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
if (!('fetch' in window)) {
throw new Error(
`Fetch not supported with this browser`);
}
if (req.method === 'JSONP') {
throw new Error(
`Attempted to construct Jsonp request without HttpClientJsonpModule installed.`);
}
return new Observable((observer: Observer<HttpEvent<any>>) => {
const request = new Request(req.urlWithParams, { method: req.method});
fetch(request)
.then((r) => {
r.json().then((body) => {
const headers = new HttpHeaders();
r?.headers.forEach((value, name) => {
headers.append(name, value)
})
observer.next(new HttpResponse({
url: r.url,
status: r.status,
statusText: r.statusText,
body: body,
headers: headers
}))
});
})
});
}
}
@Injectable({
providedIn: 'root'
})
export class FetchClient extends HttpClient {
constructor(handler: FetchBackend) {
super(handler);
}
}
StackBlitz
this.http.get('/api').subscribe(j => console.log(j));
You made it too complicated, above is all you need and it is similar to the code you have for window.fetch
. You can use the generic version and it will be typed to the expected interface as well making it even easier.
this.http.get<IModel>('/api').subscribe(j => console.log(j));
There is no need for unsubscribe
and you only need to pipe
+ map
if you want to transform the result before hand. Calling json()
was required for the "old" HttpModule which has been replaced with (technically extended by) HttpClient
as of version 4.3
And if you still prefer Promise
over an Observable
you can always call toPromise()
.
this.http.get('/api').toPromise().then(j => console.log(j));
See also HttpClient
is it wrong to use the Fetch API when developing Angular apps?
It is not wrong but there are reasons not to do it. You can inject HttpClient
and write comprehensive unit tests for various scenarios, URLs, Http verbs, etc. Doing the same if you have used window.fetch
throughout your code becomes much more difficult. HttpClient
is also richer in that you can use the type system for your results. Observables are also more feature rich than Promises and this can come in handy in more complex scenarios like sequencing calls when dealing with multiple micro services or canceling a call if additional user input is received.
So there are multiple reasons to use the HttpClient
and I can't think of a single one not to besides that it is a small learning curve.