0

I am making a call to my Web API to login. The call returns a single JSON object with a single property called token.

In my AuthService I have the following function:

  login(model: any) {
    return this.http.post('http://localhost:5000/api/auth.login', model, httpOptions).pipe(map((response: Response) => {
      console.log(response);
      if (response) {
        // localStorage.setItem('token', jwtHelper.decodeToken(response.token));
        // this.decodedToken = jwtHelper.decodeToken(response.token);
        // this.userToken = response.token;
      }
    }));
  }

Here is what the console.log of response looks like:

{token: "eyJhbGciOiJIUzXxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiO…CDNojrJEwmuIbLOjhCxzDwls22SA4Whpklh7zPFaR6g_1iQcQ"}

I could make this function return nothing to the component calling it, but in order to set the token in local storage and a few other properties, I am using pipe and then map. Is this correct? response.token is throwing an error everywhere I try to access it because typescript doesn't know about the token property on the response.

Blake Rivell
  • 13,105
  • 31
  • 115
  • 231
  • So you want to run some code but dont care about return value? Sounds like usecase for `tap` operator (previously called `do`) – Martin Adámek Aug 09 '18 at 12:57
  • @MartinAdámek I still want my Angular component to be able to subscribe to it, but there really isn't anything specific that I need to return. – Blake Rivell Aug 09 '18 at 12:59

3 Answers3

6

To get rid of that TS error, you could introduce new interface that will be designed based on your response. Also if you just want to do something with the value, its better to use tap operator (previously called do):

interface MyApiResponse {
  token: string;
}

login(model: any) {
  return this.http.post<MyApiResponse>('http://localhost:5000/api/auth.login', model, httpOptions)
    .pipe(tap((response: MyApiResponse) => {
      console.log(response.token);
      if (response) {
        // localStorage.setItem('token', jwtHelper.decodeToken(response.token));
        // this.decodedToken = jwtHelper.decodeToken(response.token);
        // this.userToken = response.token;
      }
    }));
}

If you like to return just the token (so you want to mutate the response), use map instead (as you already do).

Martin Adámek
  • 16,771
  • 5
  • 45
  • 64
  • I noticed when the user logs in unsuccessfully that the API returns 401 Unauthorized and the tap function is ignored. I added catchError to the pipe function which allows the application to proceed normally, but can I prevent the red POST error in console? The message says: 'POST http://localhost:5000/api/auth/login 401 (Unauthorized)'. – Blake Rivell Aug 09 '18 at 13:39
  • Unfortunately no (at least afaik), this is how browsers work. You fired a request that ended with error response, so you will see it in the console. It is correct approach for SPA, just catch the error with `catchError` operator and handle that case so user knows what happened. – Martin Adámek Aug 09 '18 at 13:43
  • Actually there is a way to disable those network errors in chrome, but it is a browser settings (so it will be applied only to your browser) - https://stackoverflow.com/questions/4500741/suppress-chrome-failed-to-load-resource-messages-in-console. Anyway I would strongly recommend not to do that (as you could easily loose important information about how your application runs). – Martin Adámek Aug 09 '18 at 13:50
  • Alright, I just wanted to make sure it wasn't something I should be doing. The goal is to make sure the application doesn't stop running so I want to catch the error and handle it with a user friendly message. I should be good now, thank you! – Blake Rivell Aug 09 '18 at 13:59
5

You can use map when you want to change the response and provide modified response back;

this.http.post('http://localhost:5000/api/auth.login', model, httpOptions)
.pipe(
    map((response: any) => { // <--------- MAP
        // Your code 
    })
);

But if you just want to process some functionality and dont want to change/modify the response , tap is the operator

this.http.post('http://localhost:5000/api/auth.login', model, httpOptions)
.pipe(
    tap((response: any) => { // <--------- TAP
        // Your code 
    })
);

NOTE : tap operator is available in RxJs6 / 5.5 , for older version use do operator for the same

Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122
0

You can set the type of the response to any to let compiler pass your code.

(response: any)

If you have some type that is being returned from the request, in front end you can declare that type, add token property on it and make the type of response to be that type. Now you will call post function like this

login(model: any) {
    return this.http.post<YourType>('http://localhost:5000/api/auth.login', model, httpOptions)
                    .pipe(
                            map((response: YourType) => {
                               console.log(response);
                               if (response) {
                            // localStorage.setItem('token', jwtHelper.decodeToken(response.token));
                            // this.decodedToken = jwtHelper.decodeToken(response.token);
                            // this.userToken = response.token;
                            }
                     }));
}

If you don't change response, you can also use tap operator instead of map. map is designed for mapping response to something else.

login(model: any) {
    return this.http.post<YourType>('http://localhost:5000/api/auth.login', model, httpOptions)
                    .pipe(
                            tap((response: YourType) => {
                               console.log(response);
                               if (response) {
                            // localStorage.setItem('token', jwtHelper.decodeToken(response.token));
                            // this.decodedToken = jwtHelper.decodeToken(response.token);
                            // this.userToken = response.token;
                            }
                     }));
}
Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112
  • This works! I could also create a typescript type with a token property correct? Am I doing the correct thing here using pipe and map to run additional logic in my service and still return an observable to the component? – Blake Rivell Aug 09 '18 at 12:57
  • 1
    Yes. If you don't change response, you can also use `tap` operator instead of `map`. `map` is designed for mapping response to something else – Suren Srapyan Aug 09 '18 at 12:58
  • I see, yes I should be using tap since I am not changing the response at all. – Blake Rivell Aug 09 '18 at 13:00