Angular 4 Observables Chaning Subscribers - Fired Two times - javascript

I have a strange problem while using Angular 4 Observables.
I have created a ServiceProxy.ts that manages all my HTTPS calls for my app
#Injectable()
export class ServiceProxy
{
private base_url = 'https://localhost:8443';
constructor (private http:Http) {}
public Request(route:ServiceRegistry, data : any , protocol:HttpProtocol)
{
let url : string = this.FormURI(route);
let headers = new Headers();
this.createAuthorizationHeader(headers);
if(protocol==HttpProtocol.get)
{
return this.http.post(url , data , {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
else
{
return this.http.post(url , data , {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
}
}
Now I go ahead and INJECT this ServiceProxy class in every SERVICE which needs an HTTP calls
#Injectable()
export class AuthenticationService
{
constructor(private proxy:S.ServiceProxy){ }
attemptLogin(d:L.LoginAuth): Observable<any>
{
let r:S.ServiceRegistry =S.ServiceRegistry.STAFF_LOGIN;
let p: S.HttpProtocol = S.HttpProtocol.post;
return this.proxy.Request(r,d,p);
}
}
Once that is done. I call the authentication service from my component
this.authService.attemptLogin(payload).subscribe(response =>
{
alert("Subscription Received");
if(response.status==R.STATUS.OK)
{
this.HandleLogin(JSON.stringify(response.data));
}
else
{
this.HandleFailedLogin();
}
});
Problem is - The subscription function is being called two times instead of just once.
I understand, Promise would be a better fit here as this is just one HTTP call , however I want to standardize the interface with Observables and hence not considering Promises

Observable Chain is not proper, it's broken in AuthenticationService.
Modify AuthenticationService class
#Injectable()
export class AuthenticationService
{
constructor(private proxy:S.ServiceProxy){ }
attemptLogin(d:L.LoginAuth): Observable<any>
{
let r:S.ServiceRegistry =S.ServiceRegistry.STAFF_LOGIN;
let p: S.HttpProtocol = S.HttpProtocol.post;
return this.proxy.Request(r,d,p).map(
(data) => {
return data;
}
).catch(this.handleError);
}
}

Related

Access variable from another function Angular - Get & Post [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 1 year ago.
I am writing some post and get requests to access an API in Angular.
In my post request, I create a new item and get an id for that item.
To then write a get request to get that item, i need to append the item id to the url.
How can I access the id from the post request in the get request?
I have created the variable id that gets overwritten in createItem() and can be accessed in HTML by simply writing {{id}}. But I am not able to access the overwritten content from createItem() inside of getItem(); the variable id remains empty.
My code so far:
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { HttpHeaders } from '#angular/common/http';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: '...',
}),
};
type CreatedItem = {id: string; inventory: []}
#Component({
selector: 'home-component',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
url = 'url here';
id="";
constructor(private httpClient: HttpClient) {}
ngOnInit(): void {
this.createItem();
this.getItem();
}
createItem() {
this.httpClient.post(this.url, null, httpOptions).subscribe((res) => {
const data = res;
this.id = (data as CreatedItem).id;
});
}
getItem() {
this.httpClient
.get<any>(
this.url + this.id,
httpOptions
)
.subscribe((res) => {
const data = res;
});
}
getItem()'s subscription has no idea whether or not createItem()'s subscription has completed or not which will result in the id being null when getItem() fires. The ugly fix would be to only call getItem() once createItem()'s subscription is complete and there is an id:
ngOnInit(): void {
this.createItem();
}
createItem() {
this.httpClient.post(this.url, null, httpOptions).subscribe((res) => {
const data = res;
this.id = (data as CreatedItem).id;
this.getItem(this.id)
});
}
getItem(id: string) {
this.httpClient
.get<any>(
this.url + id,
httpOptions
)
.subscribe((res) => {
const data = res;
});
}
A better way to do this would be to use a rxjs switchmap

Angular await service between components, Behavior Subject

I have an issue in my Angular web store when i refresh the window, i create a service that takes the user data from the server and then inject to the 'products' section with BehaviorSubject, my goal is to make just one request to the server:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable({
providedIn: 'root'
})
export class DataService {
private userId = new BehaviorSubject<any>('');
currentUserId = this.userId.asObservable();
constructor() { }
sendUserId(message: string){
this.userId.next(message)
}
}
This works fine but the problem is when i refresh the window in products section, in console i can see that the service takes the user data but when i getProducts() it throws an error, it seems like getProducts() makes the request before the service had the response, i need the user Id to make the products request. My question: Is there a way to await the response of BehaviorSubject and then make the getProducts() request?. This is the code in the products section:
ngOnInit(): void {
this._dataService.currentUserId.subscribe(userId => this.userId = userId);
if(this.userId.length === 0){
this.userService.getUserProfile().subscribe(
res => {
this.userDetails = res['user'];
this.userId = this.userDetails._id;
this.getProducts();
},
err => {
console.log(err);
}
);
} else {
this.getProducts();
}
}
As you can see, i do a condition to check if userId exists, if not i have to make a new user request, this fix the bug but i think there's a better way to solve this. Thanks in advance.
How about placing all your logic within the observer's next function as below:
this._dataService.currentUserId.subscribe(userId => {
if (userId.length === 0)
{
this.userService.getUserProfile().subscribe(
res => {
this.userDetails = res['user'];
this.userId = this.userDetails._id;
this.getProducts();
},
err => {
console.log(err);
}
);
} else
{
this.getProducts();
}
});

How to cache http requests in async style in angular http inteceptor?

I'm coding the angular 5 app. There is refreshAccessToken in authentication service
refreshAccessToken(): Observable<ICredentials> {
const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
const httpParams = new HttpParams()
.append('grant_type', 'refresh_token')
.append('refresh_token', this.credentials.refresh_token)
.append('client_id', Constants.CLIENT_ID)
.toString();
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post(refreshTokenUrl, httpParams, { headers })
.map((response: any) => {
this.setCredentials(response);
localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
return response;
});
}
I want to implement next alghorithm:
Any http request failed because of unauthorized with status 401
Try to get new access token from server
Repeat the request
At the time while getting new access token, new http requests can be created, in this case I want to store them and repeat after new access token was recieved. To reach this purpose I've written the interceptor.
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { AuthenticationService } from '#app/core/authentication/authentication.service';
import { Urls, UrlsService } from '#app/shared/urls';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class UnauthorizedRequestInterceptor implements HttpInterceptor {
newAccessToken$: Observable<ICredentials> = null;
constructor(
public authService: AuthenticationService,
private router: Router,
private urlsService: UrlsService) {
}
addAuthHeader(request: HttpRequest<any>) {
if (this.authService.getCredentials()) {
return request.clone({
setHeaders: {
'Authorization': 'Bearer ' + this.authService.getCredentials().access_token
}
});
}
return request;
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = this.addAuthHeader(request);
return next.handle(request).catch((error: HttpErrorResponse) => {
let handleRequests$ = null;
if (this.isNeedNewAccessToken(error, request)) {
handleRequests$ = this.handleRequestWithNewAccessToken(request, next);
}
return handleRequests$ ||
(this.isUnathorizedError(error)
? Observable.empty()
: Observable.throw(error));
});
}
logout() {
this.authService.logout();
this.router.navigate(['login']);
}
private isNeedNewAccessToken(error: HttpErrorResponse, request: HttpRequest<any>): boolean {
return this.isUnathorizedError(error)
&& this.authService.isAuthenticated()
&& this.isSignInRequest(request);
}
private getNewAccessToken(): Observable<ICredentials> {
if (!this.newAccessToken$) {
this.newAccessToken$ = this.authService.refreshAccessToken();
}
return this.newAccessToken$;
}
private isUnathorizedError(error: HttpErrorResponse) {
return error.status === 401;
}
private handleRequestWithNewAccessToken(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
return this.getNewAccessToken()
.mergeMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
})
.catch((err: HttpErrorResponse) => {
if (err.error.error === 'invalid_grant') {
this.logout();
}
return Observable.empty();
});
}
private isNotSignInRequest(request: HttpRequest<any>): boolean {
return request.url !== this.urlsService.getUrl(Urls.TOKEN);
}
}
The behaviour of this interceptor is really strange. On each mergeMap on the handleRequestWithNewAccessTokenthe angular starts new post httpRequest. I've expected that the observable returned from refreshAccessToken(function from authenticationService, code at the top) would be resolved only once. I don't understand why it is fired for each merge map? I expected the next:
I have observable - http request for token
I use mergeMap - when http request finished, all callbacks that was added with mergeMap will be executed.
I was think to store requests that I need to handle in the global variable and invoke them in the subscribe() to http request, but there is problem, that each request should be resolved in the initial stream inside interceptor. I can't do smth like this: .subscribe(token => this.httpClient.request(storedRequest) because this will create new request, so all actions should be happened inside the observable chain.
Can you please help me to find solution?
PS This solution is working, but I want to get rid off unnecessary TOKEN requests, f.e. if page need to make 5 requests and token have expired - interceptor will make 5 requests for token.
I think your code is good and all you have to do is share the request for the new token.
refreshAccessToken(): Observable<ICredentials> {
const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
const httpParams = new HttpParams()
.append('grant_type', 'refresh_token')
.append('refresh_token', this.credentials.refresh_token)
.append('client_id', Constants.CLIENT_ID)
.toString();
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post(refreshTokenUrl, httpParams, { headers })
.map((response: any) => {
this.setCredentials(response);
localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
return response;
})
.share(); // <- HERE
}
Note share operator at the end of return
EDIT:
I also think you don't ever set back this.newAccessToken$ to null. Maybe consider adding set to null to finally like this:
private getNewAccessToken(): Observable<ICredentials> {
if (!this.newAccessToken$) {
this.newAccessToken$ = this.authService.refreshAccessToken()
.finally(() => {
this.newAccessToken$ = null;
});
}
return this.newAccessToken$;
}

Angular/TypeScript : How to resolve this compilation issue?

Hell
I ma new in angular 5. I am create a login and auth service. But i cannot compile my code. Here is my code
// user.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
private loggedIn = false;
constructor(private http: Http) {
// this.loggedIn = !!localStorage.getItem('auth_token');
}
//authenticate user with dummy logic because i use a json-server
authenticate(login:string, password:string) {
console.log('Authenticate ....');
const credentials = {login:login, password:password};
let headers = new Headers();
headers.append('Content-Type', 'application/json');
var result = this.http
.get(
'/users?login?'+JSON.stringify(login),
{ headers }
);
if(result.password==password){
return true;
}
return false;
}
}
When i compile ( ng server ) i get the following error
ERROR in src/app/auth/user.services.ts(28,17): error TS2339:
Property 'password' does not exist on type 'Observable<Response>'.
Line 28 is : if(result.password==password){
I don't know what i am missing ?I try to understand the Observable concept. If you add an idea, it will help me.
Thanks
result here is an observable, you need to subscribe to it to get response.
Something like below:
var result = this.http.get(
'/users?login?'+JSON.stringify(login),
{ headers }
);
//result is an observer here, you have to subscribe to it
result.subscribe((response) => {
if(response.password==password){
return true;
}
return false;
});
You can check this awesome article: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
Use Observables properly
Use HttpClient, not old Http
You can also define a User class to make typing more strict.
// user.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
private loggedIn = false;
constructor(private http: HttpClient) {
// this.loggedIn = !!localStorage.getItem('auth_token');
}
//authenticate user with dummy logic because i use a json-server
authenticate(login:string, password:string) :Observable<boolean> {
return this.http
.get('url/whatever') //returns a User object having password
.map(user => user.password === password); // maps result to the desired true or false value
}
}
// to consume the service from a component, for example
this.userService.authenticate('myusername', 'mypassword')
.subscribe(authenticated => {
console.log('login status', authenticated)
})
You are trying to access the observable returned from the http call.
To get the information in the observable you have to subscribe to it.
For detailed information about hot to get remote data please read this:
https://angular.io/guide/http
NOTE: You should not use the deprecated angular/http. Use angular/common/http instead.

Storing response from service api call

I'm trying to wrap the http service in angular 2 using this code:
#Injectable()
export class HttpSuperService {
private baseUrl: string;
constructor(private http: Http) {
}
get(url: string): Observable<string> {
return (
this.baseUrl ?
Observable.of(this.baseUrl) :
this.http.get('/config/').map((res: Response) => res)
)
.flatMap((res: any) => {
this.baseUrl = res._body;
return this.http.get(this.baseUrl + url)
.map((res2: Response) => res2.json());
});
}
}
What I'm trying to do is making the first request to get the baseUrl for the application (the API is on another URL) but only making that request once (the first time).
it works on the first request but the second request (IE when another component is using the same service) is not working since there is something wrong with the "Observable.of". I get that i need to use Response in some way instead of the string....That lead me to start thinking about this approach. Is there a better way to do this?
This solution works but feels a little to verbose since I plan to add other methods (POST, PUT) to this service as well:
#Injectable()
export class HttpSuperService {
private baseUrl: string;
constructor(private http: Http) {
}
get(url: string): Observable<string> {
if (this.baseUrl) {
return this.http.get(this.baseUrl + url).map((res2: Response) => res2.json());
}
return this.http.get('/config/').map((res: Response) => res)
.flatMap((res: any) => {
this.baseUrl = res._body;
return this.http.get(this.baseUrl + url).map((res2: Response) => res2.json());
});
}
}
You can factor out the dependency on the retrieval of the API url into dedicated method. That method can also handle some of the intermediate steps such as url concatenation to further reduce duplication.
For example:
import {Http} from '#angular/http';
import {Inject} from '#angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/do';
let baseUrl: string;
#Inject export class HttpSuperService {
routeUrl(url: string) {
return baseUrl
? Observable.of(baseUrl + url)
: this.http.get('/config/')
.do(response => baseUrl = response.text())
.mapTo(baseUrl + url);
}
constructor(readonly http: Http) {}
get(url: string): Observable<string> {
return this.routeUrl(url)
.flatMap(this.http.get)
.map(res => res.json());
}
post(url: string, body: Something): Observable<Something> {
return this.routeUrl(url)
.flatMap(url => this.http.post(url, body))
.map(response => <Something>response.json());
}
}
I think that is reasonable, but we can do better, we can be more DRY (there is no such thing as too dry :)):
#Inject export class HttpSuperService {
constructor(readonly http: Http) {}
routeUrl(url: string) {
return baseUrl
? Observable.of(baseUrl + url)
: this.http.get('/config/')
.do(response => baseUrl = response.text())
.mapTo(baseUrl + url);
}
request<R>(url: string, makeRequest: (url: string, body?: {}) => Observable<Response>) {
return this.routeUrl(url)
.flatMap(url => makeRequest(url))
.map(response => <R>response.json());
}
get(url: string): Observable<string> {
return this.request<string>(url, fullUrl => this.http.get(fullUrl));
}
post(url: string, body: Something): Observable<Something> {
return this.request<Something>(url, fullUrl => this.http.post(fullUrl, body));
}
}
I'm not entirely satisfied with the URL handling, and I would likely refactor it further, but we have cut down on duplication significantly.
I always use this method and works fine for me, I hope it be usefull :
in my service file :
getSomething(){
return this._http.get('YOUR-API-ENDPOINT').map(res=> res.json());
}
and in my component I use this to subscribe to Observable
return this._myServiceName.getSomething().subscribe(data =>{
this.muyData=data;
}

Categories