I've read a lot of documentation, articles and different thread about how to nest observers in RxJs and Angular, I still missing something and not able to get a result at the end.
Here is my code :
page.ts
export class LiabilitiesPage implements OnInit {
constructor(
private liabilityService: LiabilityService,
private router: Router
) {}
refreshLiabilities() {
// Get the liabilities
console.log('refreshing') // passing there
this.liabilityService.getAllLiabilities().subscribe(
(response: Liability[]) => {
console.log(response); // <=== Never pass there !
if (response) {
this.liabilities = response;
} else {
// empty response code
}
}, error => {
// response error code (never passing there either)
}
}
}
liability.service.ts
// all the needed imports
#Injectable({
providedIn: 'root'
})
export class LiabilityService {
constructor(
private authService: AuthService,
private http: HttpClient,
) {}
// first try : Do not send the http request
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions()
.pipe(
tap(options => this.http.get<Liability[]>(this.url + 'me/', options))
);
}
// try 2 : Doesn't work either
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions()
.pipe(
switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options)), // at this point I tried pretty much every operators (map, mergeMap etc.)
withLatestFrom()
);
}
/* this code was working before that I transformed the authService.getOptions in observable (it was just returning the options synchronyously before)
getAllLiabilities(): Observable<Liability[]> {
return this.http.get<Liability[]>(this.url + 'me/', this.authService.getOptions());
}*/
}
auth.service.ts
public getOptions(): Observable<any> {
return new Observable((observer) => {
this.storage.get('authToken').then((token) => {
console.log('passing') // Pass here
if (token && typeof token.auth_token !== 'undefined') {
console.log('passing') // pass here as well
this.isLoggedIn = true;
this.token = token.auth_token;
}
// it is returning the value
return {
headers: this.headers.set('Authorization', 'Bearer ' + this.token),
params: new HttpParams()
};
})
});
}
I tried almost all the possible operator combinations to make it works in the liabilityService without any success.
Problem :
The problem is that my page.ts subscribes to the this.http.get<Liability[]>(this.url + 'me/', options) observer but none xhr request is fired. The http get observer is never executed and I don't understand what I'm missing there.
I'm just starting experimenting Angular, but if I understood correctly the operators should do the mapping and flattening but this looks to never happen.
Bonus question :
I'm not catching either why the initial code :
return this.http.get<Liability[]>(this.url + 'me/', this.authService.getOptions());
is returning an Observable<Liability[]>
and with the switchMap :
switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options))
It is returning a Observable<HttpEvent<Liability[]>>
If somebody has a clue and the time to answer me on that, it would be amazing
You have a problem in the promise callback then():
this.storage.get('authToken').then((token) => {
return something; // this won't work.
})
instead you can use from, which will convert your promise to an observable.
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
public getOptions(): Observable<any> {
return from(this.storage.get('authToken')).pipe(map(token => {
return headers with token.
}));
}
So you could rewrite your code like this:
auth service:
private token: string | null = null;
public getOptions(): Observable<any> {
return this.getToken().pipe(
map(token => {
return {
headers: this.headers.set('Authorization', 'Bearer ' + token),
params: new HttpParams()
};
})
);
}
private getToken(): Observable<string | null> {
if (this.token) {
return of(this.token);
}
return from(this.storage.get('authToken')).pipe(
map(token => token?.authToken || null),
tap(token => this.token = token)
);
}
then you can use a switchmap:
getAllLiabilities(): Observable<Liability[]> {
return this.authService.getOptions().pipe(
switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options))
);
}
Update
The reason for getting HttpEvent<T> is because when the overload of .get() receives an any object it leaves the http event handling entirely up to you.
If you want it to return the provided element type, you have to satisfy the proper overload.
You can achieve that doing it like so:
Instead of returning the entire options, we only return the headers, which should be enough, because we do not really have enough to say about the rest of the options.
auth service
private token: string | null = null;
public createTokenHeaders(): Observable<HttpHeaders> {
const headers = new HttpHeaders();
return addToken(headers);
}
public addToken(headers: HttpHeaders): Observable<HttpHeaders> {
return this.getToken().pipe(
map(token => headers.set('Authorization', 'Bearer ' + (token || '')))
);
}
private getToken(): Observable<string | null> {
if (this.token) {
return of(this.token);
}
return from(this.storage.get('authToken')).pipe(
map(token => token?.authToken || null),
tap(token => this.token = token)
);
}
Then use it like so:
getAllLiabilities(): Observable<Liability[]> {
const url = this.url + 'me/';
const headers = new HttpHeaders();
return this.authService.addToken(headers).pipe(
switchMap(updatedHeaders => this.http.get<Liability[]>(url, { headers: updatedHeaders }))
);
}
or:
getAllLiabilities(): Observable<Liability[]> {
const url = this.url + 'me/';
return this.authService.createTokenHeaders().pipe(
switchMap(headers => this.http.get<Liability[]>(url, { headers }))
);
}
Note: Make sure you use the headers returned from the call to addToken. Reusing your own instantiated headers will not work because setting a header always returns a new HttpHeaders object. It is immutable.
StackBlitz Example
Related
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.
here is my complete code function example:
public scan(formData: Object): Observable<any> {
let url = this.remoteUrl;
let result;
this.onlineService.isOnline$.subscribe( (isOnline) => {
if (isOnline) {
console.log('services is online connected');
result = this
._http
.post(url, formData, { headers: headers })
.pipe(map((res: any) => {
// console.log(res);
let response = res;
return response;
}),
catchError(error => {
if (error.status === 401 || error.status === 403) {
// handle error
}
return throwError(error);
}));
}else{
console.log('services are offline');
result = this.dbService.getByIndex('safety', 'code', formData['trafoStation']).subscribe( (location) => {
return location;
});
}
});
console.log(result);
return result;
};
actually, I need to run two different services based on an internet connection if the connection is available then call server API otherwise store on offline ngx-indexed-db.
i have stored data both online and offline.
getting undefined in result.
Result is undefined because it's an async operation: this.onlineService.isOnline$ has not emmited yet, but you already have return result, thus the undefined.
Also, the way you combine your observables is not right. You should NOT create new observables (and subscribe to them) in a subscribe method. That lead to weird side effects and memory leaks down the line.
Here's my proposal to get your code to work. I used the switchMap operator to return either your apiCall or your store operation based on isOnline$ value. SwitchMap is used to combine a higher observable with an inner observable and flatten the stream. It will also interupt the current subscription each time isOnline$ emits:
private _handleServices(formData, isOnline: boolean): Observable<any> {
console.log(`services are ${isOnline ? 'online': 'offline'}`);
const url = this.remoteUrl;
const apiCall$ = this._http.post(url, formData, { headers: headers })
.pipe(
catchError(error => {
if (error.status === 401 || error.status === 403) {
// handle error
}
return throwError(error);
})
);
const store$ = this.dbService.getByIndex('safety', 'code', formData['trafoStation']);
return (isOnline) ? apiCall$ : store$;
}
public scan(formData: Object): Observable<any> {
return this.onlineService.isOnline$.pipe(
switchMap((isOnline) => this._handleServices(formData, isOnline)),
tap(res => console.log(res))
);
};
Then, when you call your function in your component, you will call it like this:
this.scan(formData).subscribe(res => /* handle scan response */);
From my Typescript code, I invoke a webservice written in C#. My typescript code looks like this, and it works as expected when my service returns HTTP200, but when the server rejects the credentials and throws HTTP 400, it will not break inside the map function.
return this.http.post(this.authenticationEndpoint, params)
.map((response:Response) => {
let resp = response;
let token = response.json() && response.json().access_token;
if(token){
this.token = token;
localStorage.setItem('user', JSON.stringify({userName: userName, token:token}));
return true;
}
return false;
})
Looking at the definition of the Response class this defines properties like status, statusText and so on. Given my limited knowledge of Angular and Typescript I would assume that regardless of the Http code returned from my service, it will break inside the map function? How can I handle this case? My function return an Observable<boolean>
You need to catch the Observable Errors here's an Example:
export class ApiGateway {
baseURL = "https://myapi.com"; // or sometimes pulled from another file
constructor(private http: Http) {}
get(path, params) {
showLoadingIndicator();
let headers = this.createMySpecialHeaders();
let options = {
headers: headers
} // and whatever else you need to generalize
let fullUrl = this.baseUrl + path + '?' + this.urlEncode(params)
`;
return this.get(path, params, options)
.do(() => hideLoadingIndicator())
.map(res => res.json())
.catch(err => {
hideLoadingIndicator();
// show error message or whatever the app does with API errors etc
// sometimes rethrow the error, depending on the use case
})
}
}
Hello I need to get some response after posting json object, using toPromise, its my code, respond is undefined:
export class ApiStorage{
constructor( #Inject(Http) private http: Http ){}
rs(){
this.http.post('http://0.0.0.0:80/student/outbound', this.json, headers)
.toPromise()
.then(response => {
respond = JSON.stringify(response);
return respond; //<- edited
})
.catch((error: any) => {
...
});
}
}
then when in main component I use
send(){
respondJSON = apistorage.rs();
console.log(respondJSON);
}
respondJSON is undefined
respond will always be undefined in your code, because you are making an asynchronous call to a webservice, which you do not await before logging to console.
export class ApiStorage{
constructor( #Inject(Http) private http: Http ){}
rs() {
return this.http.post('http://0.0.0.0:80/student/outbound', this.json, headers)
.toPromise()
.then(response => {
let respond = JSON.stringify(response));
return respond;
})
.catch((error: any) => {
...
});
}
}
// rs now returns a promise, which can be used like this
// inside another function
send() {
apistorage.rs().then(res => {
console.log(res);
}
}
I understand using observable I can execute a method when the request is completed, but how can i wait till a http get is completed and return the response using in ng2 http?
getAllUser(): Array<UserDTO> {
this.value = new Array<UserDTO>();
this.http.get("MY_URL")
.map(res => res.json())
.subscribe(
data => this.value = data,
err => console.log(err),
() => console.log("Completed")
);
return this.value;
}
the "value" will is null when its returned because get is async..
your service class: /project/app/services/sampleservice.ts
#Injectable()
export class SampleService {
constructor(private http: Http) {
}
private createAuthorizationHeader() {
return new Headers({'Authorization': 'Basic ZXBossffDFC++=='});
}
getAll(): Observable<any[]> {
const url='';
const active = 'status/active';
const header = { headers: this.createAuthorizationHeader() };
return this.http.get(url + active, header)
.map(
res => {
return res.json();
});
}
}
your component: /project/app/components/samplecomponent.ts
export class SampleComponent implements OnInit {
constructor(private sampleservice: SampleService) {
}
ngOnInit() {
this.dataset();
}
dataset(){
this.sampleservice.getAll().subscribe(
(res) => {
// map Your response with model class
// do Stuff Here or create method
this.create(res);
},
(err) => { }
);
}
create(data){
// do Your Stuff Here
}
}
By looking at the angular source (https://github.com/angular/angular/blob/master/packages/http/src/backends/xhr_backend.ts#L46), it is apparent that the async attribute of the XMLHttpRequest is not getting used. The third parameter of XMLHttpRequest needs to be set to "false" for synchronous requests.
Please find code for your problem
Below is component and service file.And Code is Working fine for synchornize
import { Component, OnInit } from '#angular/core';
import { LoginserviceService } from '../loginservice.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model:any={};
constructor(private service : LoginserviceService) {
}
ngOnInit() {
}
save() {
this.service.callService(this.model.userName,this.model.passWord).
subscribe(
success => {
if(success) {
console.log("login Successfully done---------------------------- -");
this.model.success = "Login Successfully done";
}},
error => console.log("login did not work!")
);
}
}
Below is service file..
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { UserData } from './UserData';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise'
import {Observable} from 'rxjs/Rx'
#Injectable()
export class LoginserviceService {
userData = new UserData('','');
constructor(private http:Http) { }
callService(username:string,passwrod:string):Observable<boolean> {
var flag : boolean;
return (this.http.get('http://localhost:4200/data.json').
map(response => response.json())).
map(data => {
this.userData = data;
return this.loginAuthentication(username,passwrod);
});
}
loginAuthentication(username:string,passwrod:string):boolean{
if(username==this.userData.username && passwrod==this.userData.password){
console.log("Authentication successfully")
return true;
}else{
return false;
}
}
}
Another solution would be to implement a priority queue of sort.
From what I understand http requests do not get executed until you add subscribers. Therefore, you can do something like this:
Observable<Response> observable = http.get("/api/path", new RequestOptions({}));
requestPriorityQueue.add(HttpPriorityQueue.PRIORITY_HIGHEST, observable,
successResponse => { /* Handle code */ },
errorResponse => { /* Handle error */ });
This assumes that requestPriorityQueue is a service injected into your component. The priority queue would store entries in an array in the following format:
Array<{
observable: Observable<Response>,
successCallback: Function,
errorCallback: Function
}>
You would have to decide how the elements are added to your array. Finally, the following will happen in the background:
// HttpPriorityQueue#processQueue() called at a set interval to automatically process queue entries
The processQueue method would do something like this:
protected processQueue() {
if (this.queueIsBusy()) {
return;
}
let entry: {} = getNextEntry();
let observable: Observable<Response> = entry.observable;
this.setQueueToBusy(); // Sets queue to busy and triggers an internal request timeout counter.
observable.subscribe()
.map(response => {
this.setQueueToReady();
entry.successCallback(response);
})
.catch(error => {
this.setQueueToReady();
entry.errorCallback(error);
});
}
If you are able to add new dependencies you could try using the following NPM package: async-priority-queue
I looked and I couldn't find any way to make an HTTP call sync instead of async.
So the only way around this: wrap your call in a while loop with a flag. Don't let the code continue until that flag has "continue" value.
Pseudo code as follows:
let letsContinue = false;
//Call your Async Function
this.myAsyncFunc().subscribe(data => {
letsContinue = true;
};
while (!letsContinue) {
console.log('... log flooding.. while we wait..a setimeout might be better');
}
as you see, first callback waiting for a data from request and
there you can go on with your logic (or use the third one)
example:
.. subscribe( data => {
this.value = data;
doSomeOperation;
},
error => console.log(error),
() => {console.log("Completed");
or do operations here..;
}
});
How about to use $.ajax(of jQuery) or XMLHttpRequest.
It can use as asynchornize.
You should not try to make http calls behave synchronously. Never a good idea.
Coming to your getAllUser implementation it should return an observable from the function and the calling code should subscribe instead of you creating a subscription inside the method itself.
Something like
getAllUser(): Observable<UserDTO> {
return this.http.get("MY_URL")
.map(res => res.json());
}
In you calling code, you should subscribe and do whatever you want.