Angular 2 http many subscribers - javascript

I have a Page and a Service in Angular 2.
When i call the service function this.Auth.login(), it makes a http post request. My problem is, that as soon as the request returns data, i want to work with that data in the service AND the page.
I tried all kinds of stuff, but couldn't figure out how to do it.
I know that my code can't work like this because right now this.Auth.login() return a subscriber object. If i remove the '.subscribe()' in the service, it works in the Page. But thats not what i need.
I also tried to return a promise, but couldn't make it work either.
Does anybody know how i can achieve to have the data from the http.post in both controllers and work with it as soon as the request is finished?
Here's my code
Page:
import {AuthService} from '../auth/auth.service';
#Page({
templateUrl: 'build/pages/signin/signin.html'
})
export class Signin {
constructor(app: IonicApp, auth: AuthService){
this.Auth = auth;
}
signIn = function() {
this.Auth.login(this.user.email, this.user.password)
.subscribe(data => {do some stuff here});
// maybe a promise with .then can solve this
};
}
Service:
import {Http, Headers} from 'angular2/http';
import {Injectable} from 'angular2/core';
#Injectable()
export class AuthService {
private http;
constructor(http:Http) {
this.http = http;
}
login(email, password) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
// maybe i need to return this as a promise
return this.http.post('http://localhost:9000/auth/local', JSON.stringify({
email: email,
password: password
}), {
headers: headers
})
.subscribe(data => {
do some stuff here too with the data
));
// i tried to add .toPromise() but that didn't work
}
}
I left out the other lines of code so there might be some dependencies missing. It's all good though.

You can use map in the body of the Service login. i.e
.map(data => {
do some stuff here too with the data
// still bubble up
return data;
));

Ok, i don't know if this is legit but it seems to work for me:
var res = this.http.post('http://localhost:9000/auth/local', JSON.stringify({
email: email,
password: password
}), {
headers: headers
});
res.subscribe(
data => {
console.log('data');
},
err => {
console.log('err');
},
() => {
console.log('next');
}
);
return res;

Related

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();
}
});

subscribing in Angular

I am completely new to Angular and I've created a project using SpringBoot 2.0.5.RELEASE, Angular 5 and spring data to build an end to end single page java web application. I use spring boot 1.5 to expose REST APIs and angular5 with routing to build the client that will consume the APIs exposed by the server.
I've defined this component:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { User } from '../models/user.model';
import { UserService } from './user.service';
#Component({
templateUrl: './add-user.component.html'
})
export class AddUserComponent {
user: User = new User();
constructor(private router: Router, private userService: UserService) {
}
createUser(): void {
alert ('lala');
this.userService.createUser(this.user)
.subscribe( data => {
alert('User created successfully.');
});
}
}
in the page I can see the alert lala, but not 'User created successfully.' but I have no idea why
The link address when I create a user is this is this one http://localhost:4200/api/users
This is my proxy.config.json file:
{
"/api/*": {
"target": "http://localhost:8080/user-portal",
"secure": false
}
}
and from curl is fine :
curl -X POST -H "Content-Type: application/json" "http://localhost:8080/user-portal/api/users"
and user.service.ts:
import {Injectable} from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { User } from '../models/user.model';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable()
export class UserService {
constructor(private http: HttpClient) {}
private userUrl = '/api/users';
public getUsers() {
return this.http.get<User[]>(this.userUrl);
}
public deleteUser(user) {
return this.http.delete(this.userUrl + '/'+ user.id);
}
public createUser(user) {
return this.http.post<User>(this.userUrl, user);
}
}
Firstly, best not to use alert. Use console.log. Secondly, you are only handling success, you are not handling failure. Do this:
createUser(): void {
console.log('lala');
this.userService.createUser(this.user)
.subscribe(data => {
console.log('User created successfully', data);
},
err => {
console.log('There was an error', err);
},
() => {
console.log('I have completed now and nothing will ever be emitted from this Observable again');
});
}
The error handler will be executed if the HTTP response is not a success response, viz if the status code of the response is not in the 2xx range.
Check your browser network tab also to see if the HTTP request is failing.
You prob also want to debug this:
public createUser(user) {
console.log('userUrl', this.userUrl)
console.log('user', user)
return this.http.post<User>(this.userUrl, user);
}
To make sure all is as expected.
In Chrome hit F12 to open the dev tools and go to the network tab. Make sure that a request is being made to the end point and that it is not throwing and error.

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 2 JSONP injected script did not invoke callback error

I am running app on localhost://3000 with npm server
Services file:
import {Injectable} from "#angular/core";
import {Jsonp} from "#angular/http";
import 'rxjs/add/operator/map';
#Injectable()
export class futScoreService{
constructor(private _jsonp:Jsonp){}
getCompetitions(){
let queryString ='?callback=JSONP_CALLBACK';
return this._jsonp.get('http://api.football-data.org/v1/competitions/' + queryString,{method: 'Get'})
.map((res) => res.json());
}
}
Component file:
ngOnInit(){
this._futScoreService.getCompetitions().subscribe(
(comp)=>{
console.log(comp);
},
(err)=>{
console.log(err);
}
);
}
And I'm getting this error in console console-error
and on network tab I get object from API network-tab
Ok solution was making get request with http module and providing header with get request. Header part was main reason why it was failing.
let headers = new Headers({'X-Mashape-Key':'Ns0SkjyRRomshq3PgEnGoz2Zkc71p1CYnWajsnphGctvrGt46W'});
headers.append( 'Accept', 'application/json');
return this._http.get("http://api.football-data.org/v1/competitions/",{
headers: headers
})
.map((res) => res.json());
Angular is replacing JSONP_CALLBACK with
__ng_jsonp____req0_finished
but it should be
__ng_jsonp__.__req0.finished
Inspect your Network response. If you see __ng_jsonp____req0_finished({...json object...}) this is the problem.
Also, some services have different requirements for the callback query string parameter, which proves to be nasty because the error is exactly the same. I was using &callback=__ng_jsonp__.__req0.finished with MailChimp which produced the same error but the response had only a json object and no callback function. This is because MailChimp's spec is to use &c= instead of &callback=
When hardcoding the Jsonp callback (re: JSONP_CALLBACK issue) you need to account for the number of calls made, as Angular persists the state of each call. An example of what I'm doing for Mailchimp:
addEmailToList(email: string, listId: string, jsonpCalls: number, callback: any) {
const cbJsonp = '__ng_jsonp__.__req' + jsonpCalls + '.finished';
let url = [
'http://',
host,
'/subscribe',
'/post-json',
].join('');
let queryParams: URLSearchParams = new URLSearchParams();
queryParams.set('u', Config.MAILCHIMP_API_KEY);
queryParams.set('id', listId);
queryParams.set('EMAIL', email);
queryParams.set('c', cbJsonp); // non-standard; varies by service; usually 'callback'
...
}
this._InstUrl = "your url";
let params1 = new URLSearchParams();
//params.set('search', term); // the user's search value
//params.set('action', 'opensearch');
params1.set('format', 'json');
//params1.set('callback', "ng_jsonp.__req0.finished");
params1.set('callback', "JSONP_CALLBACK");
return this._jsonp
.get(this._InstUrl, { search: params1 })
.map(response => { debugger; this.Result = response.json().data })
.subscribe(
(data) => {
debugger
console.log(this.Result);
},
(error) => {
debugger
console.log(error);
});

How can I fire an app-wide event in Angular2 or observe a global variable

I have the following architecture:
Navbar is a component with:
loggedIn = false;
constructor(private authService: AuthService) {
this.loggedIn = authService.isAuthenticated();
}
displaying different links depending on the variable
the methods in authService :
isAuthenticated() : boolean {
return tokenNotExpired();
}
authenticate(email: string, password: string) : Promise<void> {
const headers = new Headers({
'Content-Type': 'application/json'});
const body = JSON.stringify({ email, password });
return this.http.post(`${apiUrl}/auth/login`, body, { headers })
.toPromise()
.then(response => {
const data = response.json();
this.user = data.user;
localStorage.setItem('id_token',data.token);
});
}
I want to get notified in navbar when isAuthenticated() returns another value.
Should I use an observable value in AuthService instead of just checking for the valid token? Should I emit an event in authenticate method's success?
I could only find info about parent-children event emmiters with #input.
Note: I am using angular2-jwt and the isAuthenticated() method is called from the auth.guard for protected routes.
You should definitely use an Observable subject in your AuthService. Take a look at this link from angular.io

Categories