I have service which i pass user token to server and return results to component but it keeps returning token: undefined while my token is exist.
Code
Note: I commented each part for better understanding.
Service
export class GroupsService {
token: any;
constructor(
private storageIonic: NativeStorage,
private env: EnvService,
private http: HttpClient,
) {
// Get token
this.storageIonic.getItem('token').then((token) => {
this.token = token.access_token;
}).catch(error => console.error(error));
}
getGroups(): Observable<any> {
// I also add this here to make sure that i will get token in any case, yet it's returning undefined
if (this.token === undefined) {
this.storageIonic.getItem('token').then((token) => {
this.token = token.access_token;
}).catch(error => console.error(error));
}
console.log('token: ', this.token); // undefined
const httpOptions = {
headers: new HttpHeaders({
Authorization : this.token, //sending token to server
Accept: 'application/json, text/plain',
'Content-Type': 'application/json'
})
};
return this.http.get(`${this.env.GROUPS}`, httpOptions).pipe(
map(groups => groups)
);
}
}
Component
export class Tab1Page implements OnInit {
groups: any[] = [];
groupsOpts = {
loop: false,
slidesPerView: 3,
slidesPerColumn: 2
};
constructor(
private groupsService: GroupsService,
private menu: MenuController,
) {
this.menu.enable(true);
this.getGroups();
}
ngOnInit() {
//
}
// I added async/await yet result hasn't change.
async getGroups() {
await this.groupsService.getGroups().subscribe((res) => {
console.log('res: ', res);
console.log('res data: ', res.data);
console.log('res data data: ', res.data.data);
for (const group of res.data) {
this.groups.push(group);
}
});
}
}
Any idea how to solve this issue?
You can use switchMap to pipe the token promise data.
import { from } from "rxjs";
export class GroupsService {
token: any;
getGroups(): Observable<any> {
// I also add this here to make sure that i will get token in any case, yet it's returning undefined
const tokenPromise =
this.token === undefined
? this.storageIonic.getItem("token")
: Promise.resolve(this.token);
return from(tokenPromise).pipe(
switchMap((token) => {
this.token = token;
const httpOptions = {
headers: new HttpHeaders({
Authorization: this.token, //sending token to server
Accept: "application/json, text/plain",
"Content-Type": "application/json",
}),
};
return this.http
.get(`${this.env.GROUPS}`, httpOptions)
.pipe(map((groups) => groups));
})
);
}
}
You need to wait for the promise to return a value before making the http request. You could put your token logic into its own function that returns a promise, and then start an observable using the RxJS from function. Once the promise returns a value you can then use switchMap to make your http request.
I have included your map in the RxJS pipe, although it's doing nothing at the moment.
export class GroupsService {
token: any;
constructor(
private storageIonic: NativeStorage,
private env: EnvService,
private http: HttpClient,
) {
}
getGroups(): Observable<any> {
return from(this.getToken()).pipe(
switchMap(() => {
const httpOptions = {
headers: new HttpHeaders({
Authorization : this.token, //sending token to server
Accept: 'application/json, text/plain',
'Content-Type': 'application/json'
})
};
return this.http.get(`${this.env.GROUPS}`, httpOptions);
}),
map(groups => groups)
);
}
private getToken(): Promise<any> {
if (this.token) {
return new Promise((resolve, reject) => resolve(this.token));
}
return this.storageIonic.getItem('token')
.then((token) => {
this.token = token.access_token;
}).catch(error => console.error(error));
}
}
this.storageIonic.getItem('token').then((token) => {
this.token = token.access_token;
}).catch(error => console.error(error));
}
This call is asychronous, you will not get the token in the next line
Try it out like this
export class GroupsService {
token: any;
constructor(
private storageIonic: NativeStorage,
private env: EnvService,
private http: HttpClient,
) {
// Get token
this.storageIonic.getItem('token').then((token) => {
this.token = token.access_token;
}).catch(error => console.error(error));
}
getGroups(): Observable < any > {
// I also add this here to make sure that i will get token in any case, yet it's returning undefined
let response = new Observable<any>();
if (this.token === undefined) {
this.storageIonic.getItem('token').then((token) => {
this.token = token.access_token;
console.log('token: ', this.token); // undefined
const httpOptions = {
headers: new HttpHeaders({
Authorization: this.token, //sending token to server
Accept: 'application/json, text/plain',
'Content-Type': 'application/json'
})
};
response = this.http.get(`${this.env.GROUPS}`, httpOptions).pipe(
map(groups => groups)
);
}).catch(error => console.error(error));
}
return response;
}
}
I am not sure about the source of your problem but you may want to change your observables to promises as I have explained in the comments.
getGroups(): Promise<any> {
// I also add this here to make sure that i will get token in any case, yet it's returning undefined
if (this.token === undefined) {
this.storageIonic.getItem('token').then((token) => {
this.token = token.access_token;
}).catch(error => console.error(error));
}
console.log('token: ', this.token); // undefined
const httpOptions = {
headers: new HttpHeaders({
Authorization : this.token, //sending token to server
Accept: 'application/json, text/plain',
'Content-Type': 'application/json'
})
};
return this.http.get(`${this.env.GROUPS}`, httpOptions).toPromise();
// Also probably the below one works too, you can try to find the proper syntax, it was something like this
return this.http.get(`${this.env.GROUPS}`, httpOptions).pipe(
map(groups => groups)
).toPromise();
}
I have changed some lines. (Change method signature, Observable => Promise and add toPromise() to return line)
You can call the method like below.
const response = await getGroups(); // This one will return the response of the request.
If you debug the code you will see that your code will wait here until it gets a response.
// IF YOU DO LIKE BELOW IT WON'T MAKE ANY SENSE
const response = getGroups(); // This will not make the call, it will just return the request object.
// In order to do the operation defined in a promise, you must call it with await prefix.
You may need apply the solution above to other parts of your code too. E.g. you are initializing the token under the constructor which it is not a good practice as I know, you may want to move that initialization under onInit() and make onInit function async. This way you can make sure that token is defined when you are making the call, otherwise your token may not beer initialized while you are making the request. And since you are not waiting your code in undefined check same thing will happen again.
(Convert your storageIonic.getItem(token: string) function to a promise and return the token from that function)
Related
I have services below that I'd like to get status code and handle if statements in it but so far I couldn't figure it out
import { Injectable } from '#angular/core';
import { EnvService } from './env.service';
import { tap } from 'rxjs/operators';
import { Observable, from } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { NativeStorage } from '#ionic-native/native-storage/ngx';
import { Plugins } from '#capacitor/core';
const { Storage } = Plugins;
#Injectable({
providedIn: 'root'
})
export class InvoicesServiceService {
token: any;
constructor(
private env: EnvService,
private http: HttpClient,
private nativeStorage: NativeStorage
) {
Storage.get({ key: 'token' }).then((token: any) => {
this.token = JSON.parse(token.value)
}).catch(error => console.error(error));
}
// All
getInvoices(): Observable<any> {
const tokenPromise =
this.token === undefined
? Storage.get({ key: 'token' })
: Promise.resolve(this.token);
return from(tokenPromise).pipe(
switchMap((token) => {
this.token = this.token;
const httpOptions = {
headers: new HttpHeaders({
Accept: 'application/json, text/plain',
'Content-Type': 'application/json',
Authorization: this.token.access_token,
}),
};
return this.http
.get(`${this.env.Dashboard}` + '/invoices', httpOptions)
.pipe(map((data) => data));
})
);
}
What I try to do is that if, status code is 403 redirect user to specific route other than that just return data.
any idea?
In component where you subscribe this service you can handle error
this.service
.getInvoices()
.subscribe((response) => {
// This is success
},
(error: HttpErrorResponse) => {
// Handle error
// Use if conditions to check error code, this depends on your api, how it sends error messages
});
Another way to handle in service itself.
return this.http
.get(`${this.env.Dashboard}` + '/invoices', httpOptions)
.pipe(map((data) => data))
.toPromise()
.then((response) => {
//Success
})
.catch((error: HttpErrorResponse) => {
// Handle error
});
Hope this helps.
The error is not always sent in the headers.
Sometimes the erros comes via HTML message, like when NGINX tells you someting before you even get to the backend:
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx</center>
</body>
</html>
In these cases you should use if (error.includes('413 Request Entity Too Large')) {...}
I am using angular 9 + universal. No errors while i run ng serve , then I build the app with npm run build:ssr and try to run with node : node dist/app/server/main.js and get the following error in terminal :
Node Express server listening on http://localhost:4000 TypeError: You
provided 'undefined' where a stream was expected. You can provide an
Observable, Promise, Array, or Iterable.
at subscribeTo (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:2547459)
at subscribeToResult (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:2775326)
at CatchSubscriber.error (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1997435)
at Observable_Observable._trySubscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952954)
at Observable_Observable.subscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952574)
at CatchOperator.call (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1996823)
at Observable_Observable.subscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952428)
at _task (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1751796)
at Observable_Observable.Observable.a.observer [as _subscribe] (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1752141)
at Observable_Observable._trySubscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952792)
As I've explored, my app does 2 api calls on start :
app.component.ts :
ngOnInit(){
get1();
get2();
}
get1() {
const loc = this.locationService.getPickupLocations().subscribe((data: Location[]) => {
this.pickupLocations = data;
this.formGroup.get(LocationFields.pickup).setValue(data[0].getId());
this.pickupLocationsList = this.pickupLocations.map((data): ISelectOption => {
return {
label: data.getName(),
value: data.getId(),
};
});
},
(error)=> {
console.log(error)
},
() => {
this.subs.add(loc);
this.pickupDateChange(this.formGroup.get(this.LocationFields.pickupDate).value);
});
}
get2() {
const drop = this.locationService.getDropOffLocations().subscribe((data: Location[]) => {
this.dropoffLocations = data;
this.formGroup.get(LocationFields.dropoff).setValue(data[1].getId());
this.dropoffLocationsList = this.dropoffLocations.map((data): ISelectOption => {
return {
label: data.getName(),
value: data.getId(),
};
});
},(error)=> {
console.log(error)
},
() => {
this.subs.add(drop);
});
}
LocationService.ts :
static locationsEndpoint = 'public/locations/rental';
getPickupLocations(): Observable<Location[]> {
const reqHeader = new HttpHeaders({ 'Content-Type': 'application/json', 'No-Auth': 'True' });
return this.http.get(`${LocationsService.locationsEndpoint}/pickup`, { headers: reqHeader }).pipe(
map((data: ILocationResponse) => this.hydrateCollectionData(data, LocationsHydrator))
);
}
getDropOffLocations(): Observable<Location[]> {
const reqHeader = new HttpHeaders({ 'Content-Type': 'application/json', 'No-Auth': 'True' });
return this.http.get(`${LocationsService.locationsEndpoint}/dropoff`, { headers: reqHeader }).pipe(
map((data: ILocationResponse) => this.hydrateCollectionData(data, LocationsHydrator))
);
}
And Interceptors :
private static BASE_URL = environment.apiUrl;
readonly HEADER_AUTHORIZATION = 'Authorization';
readonly HEADER_ACCEPT = 'Accept';
readonly HEADER_CONTENT_TYPE = 'Content-Type';
readonly ACCEPT_LANGUAGE = 'Accept-Language';
constructor(
private authService: AuthService,
private localeService: LocaleService
) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.headers.get('skip')) {
return next.handle(req);
}
if (req.url.startsWith('./assets')) {
return next.handle(req);
}
req = req.clone({
url: this._prefixUrl(req.url)
});
req = req.clone({
headers: req.headers.set(this.HEADER_ACCEPT, 'application/json')
});
req = req.clone({
headers: req.headers.set(this.HEADER_CONTENT_TYPE, 'application/json')
});
req = req.clone({
headers: req.headers.set(this.ACCEPT_LANGUAGE, this.localeService.getLocale())
});
// Set token if exists
const token = this.authService.getToken();
if (token) {
req = req.clone({
headers: req.headers.set(this.HEADER_AUTHORIZATION, `Bearer ${token}`)
});
}
return next.handle(req).pipe(
catchError((httpErrorResponse: HttpErrorResponse) => {
if(httpErrorResponse.error !== undefined){
const customError: ApiErrors = {
name: httpErrorResponse.error.name,
message: httpErrorResponse.error.message,
errors: httpErrorResponse.error.errors
};
return throwError(customError);
}
})
);
}
private _prefixUrl(path: string): string {
if (path.indexOf('/') === 0) {
path = path.substr(1, path.length - 1);
}
return `${Interceptor.BASE_URL}/${path}`;
}
I tried without these calls , tried to comment one of them.
When i disable those calls (comment them) , app works fine.
When i call them later, after i commented them, (onclick), app works fine.
When i disable interceptors , it works (SO the problem is in them, what to change ?? )
How to fix it ? And why it is happening ?
The trace seems to suggest the problem is related to CatchOperator.
I see only one place where catchError operator is used in your code
return next.handle(req).pipe(
catchError((httpErrorResponse: HttpErrorResponse) => {
if(httpErrorResponse.error !== undefined){
const customError: ApiErrors = {
name: httpErrorResponse.error.name,
message: httpErrorResponse.error.message,
errors: httpErrorResponse.error.errors
};
return throwError(customError);
}
})
);
In your code you seem to assume that the function passed to catchError will receive always an instance of HttpErrorResponse which is not the case. catchError will be used for any error and so the function passed to it can receive any type of error.
What happens if httpErrorResponse.error is null but you still have an error in the upstream Observables somewhere? According to the code above you do not return anything, so this may be the reason why the log says You provided 'undefined' where a stream was expected.
Rather than not doing anything, you can throw an error, so that you should get the details of the error causing the fact that you enter catchError operator, so something like this
catchError(err => {
if(error instanceof HttpErrorResponse && httpErrorResponse.error !== undefined){
const customError: ApiErrors = {
name: httpErrorResponse.error.name,
message: httpErrorResponse.error.message,
errors: httpErrorResponse.error.errors
};
return throwError(customError);
} else {
throw err
}
})
);
In the saveExpense() method, when passing error handling catchError (this.handleError<any>('Add Expense', [])), this method is highlighted and the error is displayed: Argument type (error:any)=>Observable<any> is not assignable to parameter type (err:any, caught:Observable<T>)=>never. How to solve?
saveExpense(userid, oExpense) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `${this.jwtToken}`
})
};
return this.http.post(`http://localhost:5555/api/expense/${userid}`, JSON.stringify(oExpense), httpOptions).pipe(
tap(
(response: ServerMessage) => console.log(response)
),
catchError(this.handleError('Add Expense', []))
);
}
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
console.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
That statement means catchError() is expecting to be given a function with a signature of (err:any, caught:Observable<T>)=>never as a parameter.
What's the signature (parameters and return type) of the function that gets passed to catchError()?
try this:
import { Observable, throwError } from 'rxjs';
saveExpense(userid, oExpense) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `${this.jwtToken}`
})
};
return this.http
.post(`http://localhost:5555/api/expense/${userid}`, JSON.stringify(oExpense), httpOptions)
.pipe(
tap((response: ServerMessage) => console.log(response)),
catchError((err) => this.handleError(err)),
);
}
private handleError(errorResponse): Observable<any> {
console.log('error', errorResponse);
return throwError(errorResponse);
}
I have a complete example of how to work with interceptors:
https://github.com/dedd1993/ngx-admin/blob/master/src/app/%40core/http/http.interceptor.ts
I'm new to angular2. In 1.* everything was fine with interceptors, just add them: and you have everywhere your headers, and you can handle your requests, when token became invalid...
In angular2 i'm using RxJs.
So i get my token:
getToken(login: string, pwd: string): Observable<boolean> {
let bodyParams = {
grant_type: 'password',
client_id: 'admin',
scope: AppConst.CLIENT_SCOPE,
username: login,
password: pwd
};
let params = new URLSearchParams();
for (let key in bodyParams) {
params.set(key, bodyParams[key])
}
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
let options = new RequestOptions({headers: headers});
return this.http.post(AppConst.IDENTITY_BASE_URI + '/connect/token', params.toString(), options)
.map((response: Response) => {
let data = response.json();
if (data) {
this.data = data;
localStorage.setItem('auth', JSON.stringify({
access_token: data.access_token,
refresh_token: data.refresh_token
}));
return true;
} else {
return false;
}
});
}
and then how can i use this token in every request? i don't want to set .header in every request. It's a bad practice.
And then: for example when i do any request, and get 401-error, how can i intercept, and get a new token, and then resume all requests, like it was in angular 1?
i tried to use JWT from here jwt, but it doesn't meet my requirements, btw in first angular i was using Restangular - and everything was fine there (also with manual on tokens:https://github.com/mgonto/restangular#seterrorinterceptor)
You can either extend the default http service and use the extended version, or you could create a method that gets some parameters (if necessary) and return a RequestOptions objects to pass default http service.
Option 1
You can create a service:
#Injectable()
export class HttpUtils {
constructor(private _cookieService: CookieService) { }
public optionsWithAuth(method: RequestMethod, searchParams?: URLSearchParams): RequestOptionsArgs {
let headers = new Headers();
let token = 'fancyToken';
if (token) {
headers.append('Auth', token);
}
return this.options(method, searchParams, headers);
}
public options(method: RequestMethod, searchParams?: URLSearchParams, header?: Headers): RequestOptionsArgs {
let headers = header || new Headers();
if (!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}
let options = new RequestOptions({headers: headers});
if (method === RequestMethod.Get || method === RequestMethod.Delete) {
options.body = '';
}
if (searchParams) {
options.params = searchParams;
}
return options;
}
public handleError(error: Response) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
Usage example:
this._http
.get('/api/customers', this._httpUtils.optionsWithAuth(RequestMethod.Get))
.map(res => <Customer[]>res.json())
.catch(err => this._httpUtils.handleError(err));
This example is using cookies to store and access the token. You could use a parameter as well.
Option 2
Second option is to extend http service, for example like this:
import { Injectable } from '#angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class MyHttp extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = 'fancyToken';
options.headers.set('Auth', token);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = 'fancyToken';
if (typeof url === 'string') {
if (!options) {
options = {headers: new Headers()};
}
options.headers.append('Auth', token);
} else {
url.headers.append('Auth', token);
}
return super.request(url, options).catch(this.handleError(this));
}
private handleError (self: MyHttp) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
And in your #NgModule:
#NgModule({
// other stuff ...
providers: [
{
provide: MyHttp,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new MyHttp(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
]
// a little bit more other stuff ...
})
Usage:
#Injectable()
class CustomerService {
constructor(private _http: MyHttp) {
}
query(): Observable<Customer[]> {
return this._http
.get('/api/customers')
.map(res => <Customer[]>res.json())
.catch(err => console.log('error', err));
}
}
Extra:
If you want to use refresh token to obtain a new token you can do something like this:
private handleError (self: MyHttp, url?: string|Request, options?: RequestOptionsArgs) {
return (res: Response) => {
if (res.status === 401 || res.status === 403) {
let refreshToken:string = 'fancyRefreshToken';
let body:any = JSON.stringify({refreshToken: refreshToken});
return super.post('/api/token/refresh', body)
.map(res => {
// set new token
})
.catch(err => Observable.throw(err))
.subscribe(res => this.request(url, options), err => Observable.throw(err));
}
return Observable.throw(res);
};
}
To be honest, I haven't tested this, but it could provide you at least a starting point.
We solved the issue with extension of AuthHttp. We added a method a on AuthHttp to set a new header dynamically like that (X-RoleId is a custom header)
declare module 'angular2-jwt' {
interface AuthHttp {
setRoleId(config: {});
}
}
AuthHttp.prototype.setRoleId = function (roleId) {
let jsThis = <any>(this);
jsThis.config.globalHeaders = [
{'Content-Type': 'application/json'},
{'X-RoleId': roleId}
];
};
I am using ionic 2 / angular 2.
I need to do a http request, but before I have to get a token using Ionic Storage.
I created a class ApiRequest for that
import {Http, Headers, RequestOptions} from '#angular/http';
import {Injectable} from '#angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Storage } from '#ionic/storage';
#Injectable()
export class ApiRequest {
access_token: string;
constructor(private http: Http, public storage: Storage) {
this.storage.get('access_token').then( (value:any) => {
this.access_token = value;
});
}
get(url) {
let headers = new Headers({
// 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.access_token,
'X-Requested-With': 'XMLHttpRequest'
});
let options = new RequestOptions({ headers: headers });
return this.http.get(url, options)
.map(res => res.json());
}
}
Then I can call like that
apiRequest.get(this.URL)
.subscribe(
data => {
this.element= data;
},
err => {
console.log(JSON.stringify(err));
});
My problem is, this.storage.get is asynchronous, http.get is asynchronous too, and I have to return http.get because I want to call subscribe outside the function.
In this case http.get is called before this.acess token received the value.
How Can I organize my code in that scenario?
This might work (not tried myself):
#Injectable()
export class ApiRequest {
access_token: string;
constructor(private http: Http, public storage: Storage) {
this.storagePromise = this.storage.get('access_token').then( (value:any) => {
this.access_token = value;
});
}
get(url) {
let headers = new Headers({
// 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.access_token,
'X-Requested-With': 'XMLHttpRequest'
});
let options = new RequestOptions({ headers: headers });
return this.storagePromise.then(
return token => this.http.get(url, options)
.map(res => res.json());
);
}
}
apiRequest.get(this.URL)
.then(observable =>
observable.subscribe(
data => {
this.element= data;
},
err => {
console.log(JSON.stringify(err));
}
);