Throwing error from subscription/observable - Angular2+ - javascript

I am attempting to throw an error from my observed function, such that I can access the err part of my subscription, but I cannot seem to get through to that part.

I'm not sure how to do it via the map way, but here is a different way:
import { Observable, of, throwError } from 'rxjs';
ngOnInit() {
this.getObs().subscribe(data => {
console.log(data);
}, err => {
console.log(err);
});
}
getObs() {
const observable = of(6);
const isError = true;
if (isError) {
return throwError("Is an error!");
}
return observable;
}
Working example: https://stackblitz.com/edit/angular-mqp4qv?file=src/app/app.component.ts

Couple ways I would handle this. Heres one way using _throw
// auth.ts
import { _throw } from 'rxjs/observable/throw';
login( username, password ) {
const body = this.jsonifyData( username, password );
return this.http.post<any>( this.apiBaseUrl + '/auth', body )
.pipe(
map( res => {
if ( 'token' in res ) {
return res.token;
}
// Unsuccessful login returns error
return _throw( res ); // from import {_throw} from 'rxjs/observable/throw';
} )
);
}
The other would be to just catch the error in the login component

I think in your component's test spec you need to mock up your authService and the login procedure. This mock does not make http call, just throw an error via throwError(new Error('Either username or password were incorrect')) immediately if, say, password does not match some predefined value. Here is some example of how to test component-http service stuff, it is a bit outdated but still actual conceptually.

Related

how to use angular pipe and subscribe correctly in request call

In my angular application I am sending a request to my backend to check credentials, after success the backend sends an token which I read. So far this works, but I had to use an pipe to make it map to a method and then make it work. But my problem now it even though I am getting 200 from the server my page will not navigate to the protected page automatically. If I enter the url manually it works this is what I tried:
authenticateUser(login: LoginModel){
this.http = new HttpClient(this.handler)
return this.http.post<JwtToken>(environment.rootUrl + 'api/authenticate', {
username: login.username,
password: login.password,
}).pipe(map(response => this.authenticateSuccess(response)))
.subscribe({
next: () => {
this.isAuthenticated = true;
this.router.navigate(['/dashboard'])
}, error: (error) => {
this.isAuthenticated = false;
console.log(error)
}
})
}
It does not enter the subscribe part after the pipe. Is there any way to make this work? I still want to have an error handling like if no error then navigate to url if error do not navigate.
EDIT:
AuthenticateSuccess method:
isUserLoggedIn(){
return !! localStorage.getItem('authenticationToken')
}
private authenticateSuccess(response: JwtToken): void {
const jwt = response.id_token;
localStorage.setItem('authenticationToken' , jwt)
this.localStorageService.store('authenticationToken', jwt);
console.log(this.localStorageService.retrieve('authenticationToken'))
this.sessionStorageService.clear('authenticationToken');
}
Authguard:
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthenticationService,
private router: Router
) {
}
canActivate(): Promise<boolean> {
return new Promise(resolve => {
if (this.auth.isUserLoggedIn()) {
resolve(true)
} else {
this.router.navigate(['authenticate'])
resolve(false)
}
})
}
}
SOLUTION:
authenticateUser(login: LoginModel) {
this.http = new HttpClient(this.handler)
return this.http.post<JwtToken>(environment.rootUrl + 'api/authenticate', {
username: login.username,
password: login.password,
}).subscribe({
next: response => {
this.isAuthenticated = true;
this.authenticateSuccess(response)
this.router.navigate(['/dashboard'])
}, error: (err) => {
console.log(err)
}, complete: () => {
console.log("finished without worry")
}
})
}
RxJs map operator is supposed to modify the content of an observable. The map operator however needs to return the same observable or another observable, for the next subscribe operation to be able to function.
In your case your map operator does not return any observable at all and therefore the subscribe method has no reason to be triggered.
You could simple return the response again in your method here
private authenticateSuccess(response: JwtToken): any {
const jwt = response.id_token;
localStorage.setItem('authenticationToken' , jwt)
this.localStorageService.store('authenticationToken', jwt);
console.log(this.localStorageService.retrieve('authenticationToken'))
this.sessionStorageService.clear('authenticationToken');
return response;
}
but I think all the code of the map method matches better directly inside the subscribe method.

axios dosen't catch error with axios-auth-refresh library

I am trying to use axios-auth-refresh library installed by npm. I stucked at problem i can't resolve. According to documentation i made code like this:
const refreshAuthLogic = (failedRequest) => {
let tokenData = JSON.parse(localStorage.getItem("REACT_TOKEN_AUTH"));
if (tokenData) {
return axios
.post(`${process.env.REACT_APP_API_URI}/Login/refresh-token`, {
departmentId: tokenData.departmentId,
jwtToken: tokenData.jwtToken,
userId: tokenData.userId,
})
.then((response) => {
console.log(response);
localStorage.setItem("REACT_TOKEN_AUTH", JSON.stringify(response.data));
failedRequest.response.config.headers["Authorization"] =
"Bearer " + response.data.jwtToken;
return Promise.resolve();
})
.catch((err) => {
console.log(`refreshed failed`);
logout();
return Promise.reject(err);
});
} else {
logout();
}
};
createAuthRefreshInterceptor(axios, refreshAuthLogic, {
statusCodes: [401],
});
Everything works almost perfect but catch never fire up so i can't logout user when refresh token expired. I get answer from api with 401 code but even if i try to put simple console log in .catch() nothing happen.
I have no idea what am i doing wrong? any ideas how to add logout() function to that code that will work?
Thanks to Sangam Rajpara, I've found a solution. You need to create a separate instance of axios.
const requestToken = axios.create();
Then use interceptors for the created instance. You can read about them on the axios page. Something like this:
requestToken.interceptors.response.use(
(res) => res,
(err) => {
// your log out action
logout();
return err;
}
);
Then, in your code instead of axios, use the instance that you created:
requestToken
.post(`${process.env.REACT_APP_API_URI}/Login/refresh-token`, {
departmentId: tokenData.departmentId, ...
You don't really need that "if" logic anymore

How to deal with Node error 'Unhandled promise rejection' so that page gets loaded anyway?

I'm creating a Next.js app served by a Node (Express) server which pulls in data through requests to an API. I keep my request endpoints in a separate api file:
const apiBase = 'http://www.example.com'
export default {
news: apiBase + '/news/'
// other endpoints
}
Then I do my requests in getInitialProps, and do conditional rendering based on whether the request gives an error or not:
static async getInitialProps( { query: { slug } } ) {
const news = await asyncReq( api.news + slug )
return { news, error: news.status }
}
render() {
return this.props.error ? <Error /> : <News />
}
asyncReq is a helper function that looks like this:
export const asyncReq = endpoint => {
return
fetch( endpoint )
.then( res => { return res.ok ? res.json() : res } )
}
This all works fine both when the request is successful and when I get 404 or 500 errors. But suppose I intentionally use a wrong endpoint:
const apiBase = 'http://www.example.com'
export default {
news: wrongApiBase + '/news/'
// other endpoints
}
In this case, Node gives me the following error because wrongApiBase is undefined:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 498)
which is what it should do, but the error causes the page to never get loaded. What am I supposed to do to handle the error? My idea was to chain a catch statement to asyncReq, but I'm not sure what I should return from it that I can then use in getInitialProps. I tried returning false but nothing changes, the page just doesn't load.
export const asyncReq = endpoint => {
return
fetch( endpoint )
.then( res => { return res.ok ? res.json() : res } )
.catch( err => { // What should I return here? )
}
+++ UPDATE +++
As it turns out, there was an issue with the error I was producing. Like I said, I was using a wrong variable name (wrongBaseApi) to trigger an error, which caused Node to never serve the page. In hindsight, it makes sense, as it's an error with Node code and not with the incoming request.
By using the right variable but assigning it a wrong value (an actually wrong API base, such as http://dahfjkdahfds.com, which is a not a Node error but an error with the request), I was able to make the solution of using a try/catch block offered by #iKoala and #DEVCNN work. So my code became:
static async getInitialProps( { query: { slug } } ) {
const news = await asyncReq( api.news + slug )
return { news }
}
render() {
// this.props.news.data
// this.props.news.error
}
and
export async function asyncReq( endpoint ) {
try {
const res = await fetch( endpoint )
return {
data: res.ok ? await res.json().then( val => { return val } ) : null,
error: res.ok ? false : res.status
}
} catch( error ) {
return { error }
}
}
I think you have to handle the error thrown from asyncReq:
static async getInitialProps( { query: { slug } } ) {
try {
const news = await asyncReq( api.news + slug )
return { news, error: news.status }
} catch (err) {
// any error, including http error 40x and 50x
return { news: null, error: err };
}
}
Not a good approach but we can achieve by adding an event listener on the root node
window.addEventListener('unhandledrejection', rj => {
this.setState({hasError: true})
})
something like this.
Since it's NodeJS, I would use process.on() (instead of window.addEventListener()) with the unhandledRejection event:
process.on("unhandledRejection", (err, promise) => {
console.log(`Unhandled rejection (promise: ${promise}, reason: ${err})`);
});
Let's say your getInitialProps calls asyncReq which in turn calls throwError method. Now if throwError method throws an error, you can catch it with the catch block in getInitialProps. So you should always put a catch block in the function that starts the function chain. To diagnose errors better, you should put the catch block in each function. But as a general rule, catch block in the first function that you call is a must.
getInitialProps = async function( { query: { slug } } ) {
try{
const news = await asyncReq( 'http://localhost:3000/' + slug )
return { news, error: news.status }
}
catch(err){
console.log('err:', err);
}
}
const throwError = () => {
throw 'New Error'
}
const asyncReq = endpoint => {
throwError();
return
fetch( endpoint )
.then( res => { return res.ok ? res.json() : res } )
}

Add a callback to Firebase Login Function

I am fairly new to React and the use of Firebase. Right now I have a Firebase.js file where I have stored and exported all of my functions and consts. This is what it looks like:
export const auth = firebase.auth();
export function performLogin(email, password) {
auth.signInWithEmailAndPassword(email, password).then(function(data) {
//Works
}).catch(function(error) {
//Does not work
});
}
So, I have this login.js that I am calling performLogin from, and I was wondering what would be the best way of doing this? How can I create a callback, or at least read any kind of return message? This is how I call performLogin:
clickLogin(e){
e.preventDefault();
performLogin(this.state.email, this.state.password);
}
And this works, as the console output tells me if the login was successful or not. However, I want to be able to use this function in order to retrieve the login status, and then determine wether or not I should prompt an error message or push the user to the admin dashboard. How would I do this?
Is it possible to call performLogin like this?
performLogin(this.state.email,this.state.password,(callback)){
if (callback == true) { //Success } else { //Error }
}
Your performLogin function could take a third parameter, a callback function that lives where your clickLogin() method lives:
export function performLogin(email, password, callback) {
auth.signInWithEmailAndPassword(email, password).then(function(data) {
//Works
callback(data);
}).catch(function(error) {
//Does not work
callback({ error });
});
}
loginResult(result) {
if(result.error) {
//failed
} else {
//logged in
}
}
clickLogin(e){
e.preventDefault();
performLogin(this.state.email, this.state.password, this.loginResult);
}
Or you could return the promise that signInWithEmailAndPassword() returns and handle it in the component.
export function performLogin(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
clickLogin(e){
e.preventDefault();
performLogin(this.state.email, this.state.password)
.then(result => //logged in)
.catch(error => //failed)
};
Take a look at the function onAuthStateChanged from "firebase/auth" package.
Here is a link to the docs:
https://firebase.google.com/docs/auth/web/manage-users

Observable Shorthand

I'm wondering if there is a better way (read: shorter) to write the following function in Typescript/Angular2. I find myself in a situation a lot of times where I'm caching observable server data and returning the cached value if it exists. My way (below) seems to be a bit overkill.
getUser(){
return Observable.create(s => {
if(!this.user)
this.connection.identity((err, res) => {
this.user = res;
s.next(res);
s.complete();
})
else{
s.next(this.user);
s.complete();
}
});
}
This is a little shorter and it handles errors:
import "rxjs/add/observable/of";
import "rxjs/add/observable/bindNodeCallback";
import { Observable } from "rxjs/Observable";
...
getUser() {
return this.user ?
Observable.of(this.user) :
Observable.bindNodeCallback(
this.connection.identity.bind(this.connection),
res => this.user = res
)();
}

Categories