Trying to figure out some basic authentication with Angular 2. I want my component to check for a token and if it exists navigate to the proper location, but if it doesn't display the error message returned by the service. Here is the the service call:
this._loginService.login(loginQuery)
.subscribe(
(token: any) => this._router.navigate( ['User', { username: user }] ),
(data) => { this.errorMessage = data.ErrorMessage; }
)
Here is the code for the service:
return this._http.post('serivcelink,
body,
{headers:headers})
.map((res : any) => {
let data = res.json();
this.token = data.TeamMember.UserName;
localStorage.setItem('token', this.token);
});
I get the token behavior I want except that I don't get access to the response object in the component, and as such can't display the error message.
How do can I check for the token and get access to the response data?
You can use an if statement to check if your token exists, else display an error message:
this._loginService.login(loginQuery)
.subscribe(
(data: any) => {
if (localStorage.getItem('token')) {
this._router.navigate( ['User', { username: user }] )
} else {
this.errorMessage = data.ErrorMessage;
}
}
)
You need to return something within the map callback, i.e. this.token in your case:
return this._http.post('serivcelink,
body,
{headers:headers})
.map((res : any) => {
let data = res.json();
this.token = data.TeamMember.UserName;
localStorage.setItem('token', this.token);
return this.token; // <-------
});
What you return will be receive as parameter into the callback of the subscribe method.
Related
I am using window.fetch in Typescript, but I cannot cast the response directly to my custom type:
I am hacking my way around this by casting the Promise result to an intermediate 'any' variable.
What would be the correct method to do this?
import { Actor } from './models/actor';
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json())
.then(res => {
// this is not allowed
// let a:Actor = <Actor>res;
// I use an intermediate variable a to get around this...
let a:any = res;
let b:Actor = <Actor>a;
})
A few examples follow, going from basic through to adding transformations after the request and/or error handling:
Basic:
// Implementation code where T is the returned data shape
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<T>()
})
}
// Consumer
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Data transformations:
Often you may need to do some tweaks to the data before its passed to the consumer, for example, unwrapping a top level data attribute. This is straight forward:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<{ data: T }>()
})
.then(data => { /* <-- data inferred as { data: T }*/
return data.data
})
}
// Consumer - consumer remains the same
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Error handling:
I'd argue that you shouldn't be directly error catching directly within this service, instead, just allowing it to bubble, but if you need to, you can do the following:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<{ data: T }>()
})
.then(data => {
return data.data
})
.catch((error: Error) => {
externalErrorLogging.error(error) /* <-- made up logging service */
throw error /* <-- rethrow the error so consumer can still catch it */
})
}
// Consumer - consumer remains the same
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Edit
There has been some changes since writing this answer a while ago. As mentioned in the comments, response.json<T> is no longer valid. Not sure, couldn't find where it was removed.
For later releases, you can do:
// Standard variation
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as Promise<T>
})
}
// For the "unwrapping" variation
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as Promise<{ data: T }>
})
.then(data => {
return data.data
})
}
If you take a look at #types/node-fetch you will see the body definition
export class Body {
bodyUsed: boolean;
body: NodeJS.ReadableStream;
json(): Promise<any>;
json<T>(): Promise<T>;
text(): Promise<string>;
buffer(): Promise<Buffer>;
}
That means that you could use generics in order to achieve what you want. I didn't test this code, but it would looks something like this:
import { Actor } from './models/actor';
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json<Actor>())
.then(res => {
let b:Actor = res;
});
Actually, pretty much anywhere in typescript, passing a value to a function with a specified type will work as desired as long as the type being passed is compatible.
That being said, the following works...
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json())
.then((res: Actor) => {
// res is now an Actor
});
I wanted to wrap all of my http calls in a reusable class - which means I needed some way for the client to process the response in its desired form. To support this, I accept a callback lambda as a parameter to my wrapper method. The lambda declaration accepts an any type as shown here...
callBack: (response: any) => void
But in use the caller can pass a lambda that specifies the desired return type. I modified my code from above like this...
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json())
.then(res => {
if (callback) {
callback(res); // Client receives the response as desired type.
}
});
So that a client can call it with a callback like...
(response: IApigeeResponse) => {
// Process response as an IApigeeResponse
}
This is specifically written for POST request. That is why it has "variables" parameter. In case of "GET" request same code will work, vriables can be optional is handled
export type FetcherOptions = {
queryString: string
variables?: FetcherVariables
}
export type FetcherVariables = {[key: string]: string | any | undefined}
export type FetcherResults<T> = {
data: T
}
const fetcher = async <T>({queryString,
variables }: FetcherOptions): Promise<FetcherResults<T>> => {
const res = await fetch(API_URL!, {
method: "POST",
headers: {
"Content-Type": "application/json",
// You can add more headers
},
body: JSON.stringify({
queryString,
variables
})
})
const { data, errors} = await res.json()
if (errors) {
// if errors.message null or undefined returns the custom error
throw new Error(errors.message ?? "Custom Error" )
}
return { data }
}
For this particular use-case:
"Fetching data from a remote resource, we do not have control and want to validate filter before injecting in our current application"
I feel recommending zod npm package
https://www.npmjs.com/package/zod
with the following fashion:
// 1. Define a schema
const Data = z.object({
// subset of real full type
name: z.string(),
// unExpectedAttr: z.number(), --> enabling this will throw ZodError
height: z.string(),
mass: z.string(),
films: z.array(z.string()),
});
// 2. Infer a type from the schema to annotate the final obj
type DataType = z.infer<typeof Data>;
(async () => {
try {
const r = await fetch(`https://swapi.dev/api/people/1/?format=json`);
const obj: DataType = Data.parse(await r.json());
console.log(obj); // filtered with expected field in Data Schema
/**
Will log:
{
name: 'Luke Skywalker',
height: '172',
mass: '77',
films: [
'https://swapi.dev/api/films/1/',
'https://swapi.dev/api/films/2/',
'https://swapi.dev/api/films/3/',
'https://swapi.dev/api/films/6/'
]
}
*/
} catch (error) {
if (error instanceof ZodError) {
// Unexpected type in response not matching Data Schema
} else {
// general unexpected error
}
}
})();
I wrote a response with an axios interceptors and send the return value of this response to a js file named handleResponse. This js file takes the return value and returns a result to me. If I get an error, I have it drop to reject.
const instance = axios.create({
baseURL:
apiName === ""
? process.env.VUE_APP_API_URL
: process.env.VUE_APP_API_URL + apiName,
withCredentials: false,
headers: headers
});
instance.interceptors.response.use(
(response) => handleResponse(response),
(error) => console.log(error)
);
My handleResponse js file inside interceptors is as follows
export const handleResponse = (response) => {
return new Promise((resolve) => {
if (response.data["Success"]) resolve(response.data["Payload"]);
else {
let msg = "";
if (response.data["Information"]) msg = response.data["Information"];
showError(msg);
reject(response);
}
});
};
Here I make it fall into catch where I call the api when it drops to the reject operation.
const cancelStorageTransfer = () => {
return StorageTransferRequestService.cancelStorageTransfer(selectedStorageTransfer.value.Id)
.then(() => {
showSuccess("Transfer İptal İşlemi Başarıyla Gerçekleşti")
storageTransferRequestSummary()
}).catch(response => {
showError(response.data.Information)
})
}
I call the api here, but I don't want to use the catch. But when I don't use it, I get "Uncauth(in promise)" error on the log screen.
Here how can I do whether to use the catch at my own will?
So I have a http get function defined in a Service class that gets an object from an endpoint and does some processing on the result:
public get(url: string, params: {}): Observable<Object> {
return this.http.get<IResult>(this.endpointRootUrl() + url, {
params: params
}).pipe(
map(res => {
if (res.Result !== 0)
throw new Error(res.Message)
else
return res.Object;
}),
catchError((err: HttpErrorResponse) => throwError(err.statusText))
)
}
This function is called from an ExampleService which gets the observable and does some more processing on it:
public loadData(): Observable<IData[]> {
return this.get("/DataLink/ListData", {}).pipe(
map(res => { return <IData[]>res }),
catchError((err: string) => throwError("There was an error retrieving data: " + err))
)
}
My questions are:
In the http get function where I check the Result property of the IResult object that gets returned from the back-end, I throw an error if the value of the result is not what is expected.
The issue is, the Message property does not get properly sent into the catchError part of the loadData function; the error message returns as "There was an error retrieving data: undefined". What am I doing wrong here?
Is this code an acceptable way to achieve what I'm trying to do? I'm open to suggestions/critique.
Thanks in advance for any help offered.
Try to move catchError before all another pipes, exept takeUntil
e.g.
Inside component:
return this.apiService.changeStatus(id, status)
.pipe(
catchError((error: HttpErrorResponse) => {
this.showMsg('error', error.error.message);
return throwError(error);
}),
tap((data: SomeResponseModel) => {
this.status = data;
})
);
Inside service
changeStatus(id: number, status: StatusesEnum): Observable<SomeModel> {
const payload: { status: StatusesEnum} = { status };
return this.http.patch<ResponseModel<SomeModel>>(this.apiUrl(`/${id}/status`), payload)
.pipe(map((data: ResponseModel<SomeModel>) => new SomeModel(data.data)));
}
In the http get function where I check the Result property of the
IResult object that gets returned from the back-end, I throw an error
if the value of the result is not what is expected. The issue is, the
Message property does not get properly sent into the catchError part
of the loadData function; the error message returns as "There was an
error retrieving data: undefined". What am I doing wrong here?
Error object doesnt have statusText property
I think you are looking for message property
catchError((err: Error) => throwError(err.message))
With that change rest of code works fine.
Example
onst data = {
Message: "Random error",
Result: 1,
Object: {
test: 1
}
}
function get(data) {
return rxjs.of(data).pipe(
rxjs.operators.map(res => {
if (res.Result !== 0) {
throw new Error(res.Message)
} else {
return res.Object;
}
}),
rxjs.operators.catchError((err) => {
return rxjs.throwError(err.message)
})
)
}
function loadData(data) {
return get(data).pipe(
rxjs.operators.catchError(err => {
return rxjs.throwError(`There was an error retrieving data: "${err}"`)
})
)
}
loadData(data).subscribe({
next: (value) => {
console.log(value)
},
error: (error) => {
console.log('error ', error)
},
complete: () => {
console.log('completed ')
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
this is a very weird problem! I'm trying to build a login form which sets a JWT token in localstorage. Other forms then use that token to post requests. I can see the token in my console.log just fine, but sometimes (like 3 out of 5 times), when I am setting localstorage.getitem('idToken'), it shows as null. This behavior most noticeably happens when I remove the console.log(idToken) from my loginUser() function (code in actions.js file - given below). What am I doing wrong? my app is built using React/Redux.
action.js
export function loginUser(creds) {
const data = querystring.stringify({_username: creds.username, _password: creds.password});
let config = {
method: 'POST',
headers: { 'Content-Type':'application/x-www-form-urlencoded' },
body: data
};
return dispatch => {
// We dispatch requestLogin to kickoff the call to the API
dispatch(requestLogin(creds));
return fetch(BASE_URL+'login_check', config)
.then(response =>
response.json().then(user => ({ user, response }))
).then(({ user, response }) => {
if (!response.ok) {
// If there was a problem, we want to
// dispatch the error condition
dispatch(loginError(user.message));
return Promise.reject(user)
} else {
localStorage.setItem('idToken', user.token);
let token = localStorage.getItem('idToken')
console.log(token);
// if I remove this log, my token is returned as null during post.
dispatch(receiveLogin(user));
}
}).catch(err => console.log("Error: ", err))
}
}
here's my POST request:
import axios from 'axios';
import {BASE_URL} from './middleware/api';
import {reset} from 'redux-form';
let token = localStorage.getItem('idToken');
const AuthStr = 'Bearer '.concat(token);
let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr }
};
export default (async function showResults(values, dispatch) {
console.log(AuthStr);
axios.post(BASE_URL + 'human/new', values, headers)
.then(function (response) {
console.log(response);
alert("Your submit was successful");
//dispatch(reset('wizard'));
}).catch(function (error) {
console.log(error.response);
alert(error.response.statusText);
});
});
This GET request works everytime, BTW:
getHouses = (e) => {
let token = localStorage.getItem('idToken') || null;
const AuthStr = 'Bearer '.concat(token);
axios.get(BASE_URL + 'household/list', { headers: { Authorization: AuthStr } }).then((response) =>
{
let myData = response.data;
let list = [];
let key =[];
for (let i = 0; i < myData._embedded.length; i++) {
let embedded = myData._embedded[i];
list.push(embedded.friendlyName);
key.push(embedded.id);
}
this.setState({data: list, key: key});
})
.catch((error) => {
console.log('error' + error);
});
}
I'm at my wit's end! Please help!
The localStorage.setItem() is a asynchronous task, and sometimes you run let token = localStorage.getItem('idToken') just after the setItem will fail, so you get a null, so please put the getItem operation some later, have a try, it will be different :
setTimeout(function() {
let token = localStorage.getItem('idToken');
dispatch(receiveLogin(user));
}, 50);
Move your token logic (i.e. localStorage.getItem('idToken');) inside the exported function and it should work
export default (async function showResults(values, dispatch) {
let token = localStorage.getItem('idToken');
const AuthStr = 'Bearer '.concat(token);
let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr
}
};
axios.post(BASE_URL + 'human/new', values, headers)...
There can't be a case where you set a key value in localstorage and then it returns you null, immediately in the next line.
localStorage.setItem('idToken', user.token);
let token = localStorage.getItem('idToken');
This will only happen if your user.token value is null.
Maybe the case here is your thennable function not returning value to your next then like this:
....
.then(response =>
// return response to your next then function
// this will be passed to next then function as params
return response.json();
).then(({ user, response }) => {
....
Make a function whose return the value or a default value
const [hideTyC, setHideTyC] = useState(false);
const loadTyCFlag = (): any => {
if (
localStorage.getItem("tyc") !== null ||
localStorage.getItem("tyc") !== undefined
) {
return localStorage.getItem("tyc") || false;
}
};
useIonViewDidEnter(() => {
hideTabBar();
setHideTyC(loadTyCFlag());
});
I want to automatically get the user data like below but I get an error at the observe line:
Uncaught Error: [mobx] Invariant failed: Cannot obtain atom from [object Object]
class AuthStore {
#observable loggedIn = false;
#observable userData;
constructor() {
observe(this, 'loggedIn', (change) => {
if (change.loggedIn) {
this.getUserData();
}
});
}
async getUserData() {
const response = await ajax({
url: '/users/me',
method: 'GET'
});
const data = await response.json();
//todo handle some errors here
this.userData = data.user;
}
}
Have you solved it already? If not, try using reaction instead of observe:
reaction(
() => this.loggedIn,
loggedIn => loggedIn && this.getUserData()
)