Angular 2 http request to Play REST server - javascript

I'm implementing an Angular 2 service which gets JSON data from play framework server by http request.
http://localhost:9000/read returns JSON data like [{"id":1,"name":"name1"},{"id":2,"name":"name2"}].
And here's my Angular service code (from this tutorial https://angular.io/docs/ts/latest/guide/server-communication.html):
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Hero } from './hero';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class HttpService {
private heroesUrl = "http:localhost:9000/read"; // URL to web API
constructor (private http: Http) {}
getHeroes (): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
But the request in browser looks like this:
GET XHR http://localhost:4200/localhost:9000/read [HTTP/1.1 404 Not Found 5ms]
So Angular creates relative URL, when absolute URL is needed.
So could I...
1)Fix it in code.
2)Or make Angular 2 and Play run on same port.
3)Use JSONP or something else.

The reason to your relative url is simply because you have a typo, which causes this. Instead of
private heroesUrl = "http:localhost:9000/read";
it should be
private heroesUrl = "http://localhost:9000/read";
No other magic should probably not be needed here. You might run into a cors issue. But since this is a playground, and not actual development, in case of CORS you can enable CORS easily in chrome. But this is naturally NOT recommended in a real-life app. But for playing, that would do just fine.

Related

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: Make Post Request From ErrorHandler?

I am trying to send any errors, exceptions that Angular is catching to my server. I made my own class called GlobalErrorHandler that is extending ErrorHandler. Please check below
import { ErrorHandler, Injectable, Injector } from "#angular/core";
import {HttpHeaders, HttpClient} from "#angular/common/http";
import { TestServiceService } from "../_services/test-service.service";
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(
private injector: Injector,
private http: HttpClient,
private service: TestServiceService,
) {}
url = 'http://127.0.0.1:4000/post';
handleError(error) {
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
try {
let msg = JSON.parse(error)
console.log('>>>>>>>>> message is ', msg)
this.http.post(this.url, msg, httpOptions);
}
catch (e) {
console.log('>>>>>>>>> err in catch is ', e)
}
}
}
I am able to console.error(error) whenever an error occurs, but I cannot make a post request to my server.
What am I missing in my code to make post request from ErrorHandler?
After changing the code to the following (replacing JSON.parse with JSON.stringify and catching the post errors successfully):
handleError(error) {
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
let subscription = this.http.post(this.url, JSON.stringify(error), httpOptions).subscribe(
(data => {console.log('>>>>>>> data is ', data));subscription.unsubscribe();},
error => {console.log('>>>>>>>> err', error);subscription.unsubscribe();}
)
}
The error was discovered to be on the serverside, but the code above should be useful to anyone trying to trasmit clientside errors(in Angular2+) to the server provided that the server has been implemented correctly.

Angular Http.get request from servlet of project on local server (json data returns correctly, but not displaying, does work with a mock api)

Error in console:
Error with getting users from service. Response with status: 404 Not Found for URL: http://localhost:8080/ChatApp/GetAllUsersServlet
Same problem occurs when I deployed the external project somewhere, so with url:
http://java.cyclone2.khleuven.be:38034/ChatApp/GetAllUsersServlet
You can see for yourself is a working url with json in it, but stil 404 error.
Angular code expecting json from servlet running on local server:
export class UserService {
// private usersUrl = 'api/users'; // mock api
private usersUrl = 'http://localhost:8080/ChatApp/GetAllUsersServlet'; // external users from local server
private headers = new Headers({'Content-Type': 'application/json'});
constructor(private http: Http) { }
getUsers(): Promise<User[]> {
return this.http.get(this.usersUrl)
.toPromise() // Http.get returns RxJS Observeable, converted to Promise here
.then(response => response.json().data as User[]) // .data for mock inMemoryDataService
.catch(this.handleError);
}
What Servlet returns:
[{"fname":"TestFname","password":"test","gender":"Female","name":"TestName","id":1,"email":"test#test.com","age":21,"username":"Test","status":"offline"},{"fname":"Test4Fname","password":"test","gender":"Female","name":"Test4Name","id":4,"email":"test4#test.com","age":21,"username":"Test4","status":"offline"},{"fname":"Test3Fname","password":"test","gender":"Female","name":"Test3Name","id":3,"email":"test3#test.com","age":28,"username":"Test3","status":"offline"},{"fname":"Test2Fname","password":"test","gender":"Male","name":"Test2Name","id":2,"email":"test2#test.com","age":22,"username":"Test2","status":"offline"}]
This exact thing in a mock api, does give correct result:
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
let users = [{"fname":"TestFname","password":"test","gender":"Female",
"name":"TestName","id":1,"email":"test#test.com","age":21,"username":"Test","status":"offline"},
{"fname":"Test4Fname","password":"test","gender":"Female",
"name":"Test4Name","id":4,"email":"test4#test.com","age":21,"username":"Test4","status":"offline"},{"fname":"Test3Fname","password":"test","gender":"Female","name":"Test3Name","id":3,"email":"test3#test.com","age":28,"username":"Test3","status":"offline"},
{"fname":"Test2Fname","password":"test","gender":"Male",
"name":"Test2Name","id":2,"email":"test2#test.com","age":22,"username":"Test2","status":"offline"}]
return {users};
}
}
Any help would be appreciated, since I really don't know why it won't work. Tried something similar but just less json data and that works.
Yes, the server for the servlet is running locally.
getUsers() gets used and displayed by this, but since it works with mock data, this should be okay?:
export class UsersComponent {
users: User[];
selectedUser: User;
constructor(
private userService: UserService,
private router: Router) { }
gotoInfo(): void {
this.router.navigate(['/info', this.selectedUser.username]);
}
onSelect(user: User): void {
this.selectedUser = user;
}
getUsers(): void {
this.userService.getUsers().then(users => this.users = users);
}
ngOnInit(): void {
this.getUsers();
}
}
Servlet (cors enabled):
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
UserDB db = new UserDB();
JSONArray array = new JSONArray();
for (User users: db.getAll()) {
try {
array.put(users.getJSONObject());
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// setting the response type to json
response.setContentType("application/json");
// setting the CORS request
response.setHeader("Access-Control-Allow-Origin", "*");
response.getWriter().write(array.toString());
Previously using in-memory-web-api will mess with your http-requests unless you remove something like the following InMemoryWebApiModule.forRoot(InMemoryDataService) from your NgModule, then your requests should be going fine.
EDIT: Realized based on code comment that you knew the following:
After this is done, I can also point out that you have a problem in your get request, as it stands you will not get any data in your component. Your response just contains an array, not data, so it should be just:
.then(response => response.json() as User[])

Angular 2 HTTP GET to Node backend for list of file names in directory

I'm trying to use an Angular 2 HTTP GET request to simply connect with a Node/Express backend that responds with a list of the file names in a certain folder using the fs.readdir method.
I set up the Angular 2 request as a service:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import './rxjs-operators';
#Injectable()
export class PhotoService {
constructor (private http: Http) {}
private photosUrl = '/api/photos'; // URL to web API
getPhotos() : Observable<string[]> {
return this.http.get(this.photosUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body.data || { };
}
private handleError (error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}
and then called this service from a component:
ngOnInit() {
this.photoService.getPhotos()
.subscribe(
photos => this.fileList = photos,
error => this.errorMessage = <any>error);
}
This is the Node backend (with Express set up as per conventions):
//Photo Service
app.get('/api/photos', function(req, res) {
fs.readdir('./uploads', function(error, files) {
if (error) {
throw error;
}
else {
res.end(files);
}
});
});
As seen, the HTTP request calls a GET method to http://localhost:3000/api/photos and the Node backend is supposed to receive that request and send back an array of strings that have the names of files in the 'uploads' folder.
However it does not seem to be working. I think I'm getting confused with the format in which the Node API sends the response and how that works with the Observable type that Angular uses in the service.
Your Angular 2 code looks good to me. But in your Node backend you should not send data with res.end() (see the documentation). Correct would be res.send(files); or in your case res.json(files); which will also set the right Content-Type header.

Use JavaScript function in Angular 2 app

Trying to use a JS function within an Angular2 service but getting require is not a function in the console. I'm not sure how to include the external JS file. It's at the same directory level as the service using it.
I believe that the declare statement is legit. true?
I'm not sure how to handle the invalid require statement. It produces the
error "Require is not a function"
Service file: analytics.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
//how I thought i was supposed to make the JS function available
declare var AlchemyAPI: any;
#Injectable()
export class AnalyticsService {
constructor (private http: Http) {}
getResponse () {
//not sure how to handle this require. I know that its not allowed
though
var AlchemyAPI = require('./alchemyapi');
//instance of external JS file function
var alchemyapi = new AlchemyAPI();
var myText = "Whoa, AlchemyAPI's Node.js SDK is really great, I can't
wait to build my app!";
return this.http.get(alchemyapi.sentiment("text", myText, {}))
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
console.log(body);
return body.data || { };
}
private handleError (error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server
error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
External JS file thaat makes remote API call: alchemyapi.js (at the same directory level)
var http = require('http');
var fs = require('fs');
//Make the class available
exports = module.exports = AlchemyAPI;
You can remove require and add JS file in script tag of you index.html. declare states typescript compiler that var/function with that name will be available at runtime so won't give compile time errors.
No need to use require. You could just import it like you did with #angular and rxjs:
import { AlchemyAPI } from './alchemyapi';
Now you can use AlchemyAPI inside your code anyway you want.

Categories