4

Using typescript, Angular 6+ and rxjs, how to best represent to the caller that an HTTP response content is empty (Content-Length=0)?

Without specifying types in the method call (get, post, etc.), an Observable<Object> is returned, which is misleading regarding the response content, as one can be tempted to use the given object:

let response: Observable<Object> = httpClient.post('...');
response.subscribe(o => ...);

In this case, o is always null, but this is not explicit nor checked by the compiler.

A better solution would be to return Observable<null> but I find the semantic to be ambiguous, as null could also refer to a null data (eg, a customer without phone number would have a phoneNumber property set to null).

AskNilesh
  • 67,701
  • 16
  • 123
  • 163
Clément
  • 729
  • 6
  • 18

3 Answers3

3

What you're looking for actually exist and its correct type is Observable<void>. Like you said null is a regular value that can be produced by any Observable. In RxJS 6 this is represented by empty() which is like any other Observable but it doesn't emit any next value and just sends the complete signal.

import { empty, EMPTY } from 'rxjs';

empty().subscribe(...) // will receive just the `complete` notification

// or you can use EMPTY

EMPTY.subscribe(...)

By the way you can also turn any Observable into Observable<void> using the ignoreElements() operator.

There're also never() and NEVER Observables but don't mistake them for empty() because these don't emit anything (not even the complete notification).

martin
  • 93,354
  • 25
  • 191
  • 226
  • 1
    `Observable` is technically working but its semantic is hard to reason about: does it emit something? in what sense is the value "void"? `empty()` is good but like I said to @Sunil Singh, its too bad that the use of the subscribe method changes depending on whether there is a content or not (using sometimes `next` and sometimes `complete`). – Clément Oct 17 '18 at 12:41
  • 1
    Well, it has to change. How would you distinguish a valid value from a situation where you don't want to emit anything? That's what `complete` and `error` notifications are for. Of course you can use `null` but it seems like you wanted to avoid this. The `void` type is a TypeScript type that represents nothing (like `NULL` in a database). Then `Observable` is an Observable that emits `next` items of type `T`. So `Observable` is an Observable that emits nothing. – martin Oct 17 '18 at 12:45
2

You can create a method for send a request as Observable like:

 // Add a new comment
    addComment (body: Object): Observable<Comment[]> {
        let bodyString = JSON.stringify(body); // Stringify payload
        let headers      = new Headers({ 'Content-Type': 'application/json' }); // ... Set content type to JSON
        let options       = new RequestOptions({ headers: headers }); // Create a request option

        return this.http.post(this.commentsUrl, body, options) // ...using post request
                         .map((res:Response) => res.json()) // ...and calling .json() on the response to return data
                         .catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
    }   

    // Update a comment
    updateComment (body: Object): Observable<Comment[]> {
        let bodyString = JSON.stringify(body); // Stringify payload
        let headers      = new Headers({ 'Content-Type': 'application/json' }); // ... Set content type to JSON
        let options       = new RequestOptions({ headers: headers }); // Create a request option

        return this.http.put(`${this.commentsUrl}/${body['id']}`, body, options) // ...using put request
                         .map((res:Response) => res.json()) // ...and calling .json() on the response to return data
                         .catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
    }   

    // Delete a comment
    removeComment (id:string): Observable<Comment[]> {
        return this.http.delete(`${this.commentsUrl}/${id}`) // ...using put request
                         .map((res:Response) => res.json()) // ...and calling .json() on the response to return data
                         .catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
    }

Or simply use of HttpClient like:

getConfigResponse(): Observable<HttpResponse<Config>> {
  return this.http.get<Config>(
    this.configUrl, { observe: 'response' });
}

    addHero (hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
    .pipe(
           catchError(this.handleError('addHero', hero))
         );
    }
AmirReza-Farahlagha
  • 1,204
  • 14
  • 26
1

You can use Observable.empty()

or

empty() if you have imported from rxjs

Sunil Singh
  • 11,001
  • 2
  • 27
  • 48
  • It's not perfect since it requires to change the subscribe method (to use the `complete` callback instead of `next`). But I agree that `empty` is the best operator to convey the meaning of a _completed_ HTTP request with _empty_ content. – Clément Oct 17 '18 at 12:28
  • @Clément You can use `subscribe({ complete: () => ... })` – martin Oct 17 '18 at 12:34