London804
London804

Reputation: 1126

Type 'void' is not assignable to type 'Observable<any>' How can I fix this?

I'm trying to pass the return value of my API to my BehaviorSubject, but I'm encountering the Typescript error above. It's not clear to me how to fix it. The line of code that is causing the error is return this.sleepReponse.next(score) How should this be passed to my BehaviorSubject?

 export class SleepService {
    private endpoint = '';
    private sleepResponse = new BehaviorSubject<any>({});
    currentResponse = this.sleepResponse.asObservable();
  
    constructor(private http: HttpClient) { }

    public postSleepDuration(score: string, forceResponse?: boolean, errorCode?: number): Observable<any | HttpErrorResponse> {

        if (forceResponse === undefined) {
            return this.http.post<any>(this.endpoint, score );
        } else {
            const accounts = RESULTS_MOCK;
            const error = mockError(errorCode, this.endpoint);
            console.log('forceREsponse', of(accounts))

            let response = !!forceResponse ? of(accounts) : throwError(error);
            return this.sleepResponse.next(response)
        }
    }
}

const RESULTS_MOCK: any = {
    count: 1,
    next: null,
    previous: null,
    status: 200,
    results: [
        {
            id: "1",
            name: "Asleep and In bed results"
        }
    ]
}

Upvotes: 1

Views: 1817

Answers (2)

London804
London804

Reputation: 1126

Using a pipe worked.

    public postSleepDuration(score: string, forceResponse?: boolean, errorCode?: number): Observable<any | HttpErrorResponse> {

        if (forceResponse === undefined) {
            return this.http.post<any>(this.endpoint, score );
        } else {
            const accounts = RESULTS_MOCK;
            const mockedError = mockError(errorCode);

            return of(accounts).pipe(
                tap(response => {
                    this.sleepResponse.next(response)
                }),
                catchError(error => 
                    throwError(mockedError) )
            )
        }
    }

Upvotes: 2

msbit
msbit

Reputation: 4320

One of the nicest things about Angular is the pattern of passing all the dependencies in via the constructor, and having many of the dependencies defined as abstract classes instead of concrete implementations.

So, if you wanted to test SleepService, you can move the mocking logic from inside SleepService to its dependencies, leaving SleepService quite simple:

import { Observable } from 'rxjs';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

export class SleepService {
  private endpoint = '';

  constructor(private http: HttpClient) { }

  postSleepDuration(score: string): Observable<any | HttpErrorResponse> {
    return this.http.post<any>(this.endpoint, score);
  }
}

and provide a HttpClient instance with a mocked HttpHandler dependency. This HttpHandler implementation only requires a single method, handle(), which has access to the HttpRequest being made so it can react as appropriate for the tests.

It can be as simple as a hard-coded response/error, for example in the success case, something like:

import { of, Observable } from 'rxjs';
import { HttpClient, HttpEvent, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
import { SleepService } from './sleep-service';

const RESULTS_MOCK: any = {
  count: 1,
  next: null,
  previous: null,
  status: 200,
  results: [
    {
      id: '1',
      name: 'Asleep and In bed results'
    }
  ]
}

class OkHandler implements HttpHandler {
  handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
    return of(new HttpResponse({body: RESULTS_MOCK}));
  }
}

const okService = new SleepService(new HttpClient(new OkHandler()));
okService.postSleepDuration('test').subscribe({
  next(x) { console.log(JSON.stringify(x, null, 2)); }
});

and in the failure case, something like:

import { throwError, Observable } from 'rxjs';
import { HttpClient, HttpErrorResponse, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http';
import { SleepService } from './sleep-service';

class ErrHandler implements HttpHandler {
  handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
    return throwError(new HttpErrorResponse({ error: mockedError }));
  }
}

const errService = new SleepService(new HttpClient(new ErrHandler()));
errService.postSleepDuration('test').subscribe({
  error(x) { console.log(JSON.stringify(x, null, 2)); }
});

Or you can provide a single HttpHandler implementation which has different behaviour based on its state, something like:

import { of, throwError, Observable } from 'rxjs';
import { HttpClient, HttpErrorResponse, HttpEvent, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
import { SleepService } from './sleep-service';

function mockError(errorCode: number, endpoint: string) {
  return `${errorCode}: ${endpoint}`;
}

const RESULTS_MOCK: any = {
  count: 1,
  next: null,
  previous: null,
  status: 200,
  results: [
    {
      id: '1',
      name: 'Asleep and In bed results'
    }
  ]
}

class Handler implements HttpHandler {
  errorCode?: number;

  handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
    if (this.errorCode !== undefined) {
      return throwError(new HttpErrorResponse({ error: mockError(this.errorCode, request.url) }));
    } else {
      return of(new HttpResponse({body: RESULTS_MOCK}));
    }
  }
}

const handler = new Handler();
const service = new SleepService(new HttpClient(handler));

service.postSleepDuration('test').subscribe({
  next(x) { console.log(JSON.stringify(x, null, 2)); }
});

handler.errorCode = 42;

service.postSleepDuration('test').subscribe({
  error(x) { console.log(JSON.stringify(x, null, 2)); }
});

Upvotes: 1

Related Questions