I am struggling to set up a service in angular2 that should communicate with a REST backend.
I was trying to set up a minimal example that should sent requests to a rest backend and process the response. The Service is called correctly and my console shows the message
'calling the star wars api';
The problem is that I was checking the xhr tab in the network section of the browser dev tools and no xhr requests are fired.
It seems like the functions extractData and handleError do not seem to be called either although I defined them in map and catch.
Am I missing something here? Why is there no xhr request sent when I call this service?
This is my Service:
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { Token } from './token';
#Injectable()
export class UserService {
constructor(private http: Http) {
}
getAll() : Observable<Token[]>{
console.log('calling the star wars api');
let people$ = this.http
.get('http://swapi.co/api/people', {headers: this.getHeaders()})
.map(this.extractData)
.catch(this.handleError);
return people$;
}
private getHeaders(){
let headers = new Headers();
headers.append('Accept', 'application/json');
return headers;
}
private extractData(res: Response) {
console.log('data extracted!');
let body = res.json();
return body.data || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
console.log('an error ocurred');
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);
}
}
I set up the project, component and service with angular-cli. I am using angular2 version 2.4.0
thanks to 0x2D9A3's comments I was able to resolve the issue.
The problem was the way that I called the UserService from my component.
this.userService.getAll()
just called the service but did not fire any XHR request.
Howewer,
this.userService.getAll().subscribe((result) => {
if (result) {
console.log('successfull xhr request');
}
}
Did call the xhr request successfully because this is the way how an Observable has to be consumed.
This is from the angular docs
Think of an Observable as a stream of events published by some source.
To listen for events in this stream, subscribe to the Observable.
These subscriptions specify the actions to take when the web request
produces a success event (with the data in the event payload) or
a fail event (with the error in the payload).
Related
The call of GetData from the controller of a component is done too early, I would like it to wait for the end of the identification before triggering.
When the page is loading, there is a call to the server for identification:
return this.http.get<AuthInfo>('.../Auth/login').pipe(
map((authInfo: AuthInfo) => {
// ... authentification completed
return authInfo;
}),
);
Then, from several other components, they will be able to call the server : (with a JWT token previously provided)
this.serviceA.methodB().subscribe(
result => { ... }
)
methodB(): Observable<any> {
return this.http.get<any>('.../Data/GetData').pipe(
map(result => {
...
return result;
}),
);
}
I want the methodB to be called only if the identification is finished.
So if any method is called, it must be able to pause if the identification process is not finished.
The difficulty here is that the methodB is called from other places and during or after the execution of the login method.
Is this possible with RxJs?
You could expose the authentication information in a BehaviorSubject:
class AuthenticationService {
private subject = new BehaviorSubject(null);
public info = subject.asObservable();
authenticate() {
this.http.get<AuthInfo>('.../Auth/login').subscribe(x => this.subject.next(x), e => this.subject.error(e))
}
}
Call authenticate wherever you need in your app (e.g: on a login button)
Then I suggest you to use an interceptor in order to make sure that requests are authenticated before they are sent:
class AuthenticationInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return authenticationService.info.pipe(
tap(x => /* If you need to attach the JWT to an HTTP header, do it there */),
switchMap(() => next.handle(req))
)
}
}
You could provide the JWT-Token as an ReplaySubject within some kind of service (probably your AuthenticationService, if you have one). Then you could use an HttpInterceptor to intercept every HttpRequest from your application and add the JWT Token to the Request Headers.
class AuthenticationService {
public token = new ReplaySubject<string>(1)
public login() {
// your login logic here
this.token.next(theToken)
}
}
class AuthInterceptor implements HttpInterceptor {
constructor (private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.authService.token.pipe(
mergeMap(token => {
req = req.clone({
headers: new HttpHeaders({
'Authorization': `bearer ${token}`,
})
});
return next.handle(req);
})
)
}
}
Please note: I haven't tested this code yet and I'm not 100% sure if the ReplaySubject is the right choice here. But using an HttpInterceptor and a shared service for your token should be the way to go.
You probably also need some additional logic in your AuthInterceptor to not intercept the login request and other requests in your application that don't need the token.
For instructions on how to provide and use interceptors, see the Angular Http Client Guide.
I've got an angular site that's reporting error messages to the console, but it's working on screen. I suspect it's due to how the page renders, but after googling the error and Angular rendering I can't see how to fix it.
This is how the console looks:
This is the service that's handling the API calls:
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from "#angular/http";
#Injectable()
export class WmApiService {
private _baseUrl = "http://localhost:58061/";
tempuser = "WebDevelopWolf";
modules: any;
constructor(private _http: Http) {
console.log('Wavemaker API Initialized...');
}
// On successful API call
private extractData(res: Response) {
let body = res.json();
return body || {};
}
// On Error in API Call
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
// Basic Get W/ No Body
getService(url: string): Promise<any> {
return this._http
.get(this._baseUrl + url)
.toPromise()
.then(this.extractData)
.catch(this.handleError);
}
// Basic Post W/ Body
postService(url: string, body: any): Promise<any> {
console.log(body);
let headers = new Headers({'Content-Type': 'application/json'});
return this._http
.post(this._baseUrl + url, body, {headers: headers})
.toPromise()
.then(this.extractData)
.catch(this.handleError);
}
}
And finally the call to the service:
ngOnInit() {
this.getUserProfile();
}
// Fill the user profile information
getUserProfile() {
this._wmapi
.getService("User/" + this._wmapi.tempuser)
.then((result) => {
// Push the user to UI
this.userProfile = result;
// Set the user avatar
this.userAvatar = "../assets/users/profile/" + this.userProfile.Username + ".png";
})
.catch(error => console.log(error));
}
I've had a couple of people tell me in the past that I shouldn't be using promises because they're outdated, but it's just what familiar with from working with Ionic a couple of years back - however, if there is a better way to do it I'm definitely open to suggestion, especially if it's the promise that's causing the issue.
try:
<div>{{some-value?.UserFullName}}</div>
Your some-value object doesn't have the value until the API response arrives. Then use ? to apply the null check until the response arrives.
The problem is that angular is trying to render your component and this.userProfile is not instantiated yet by that moment, so you are trying to resolve the props of undefined.
You need to handle the case when there is no userProfile, so you can either use ngIf for that section of template, or use getter to get those props, or perform check directly in template {{userProfile && userProfile.someProp}}
While using NestJS to create API's I was wondering which is the best way to handle errors/exception.
I have found two different approaches :
Have individual services and validation pipes throw new Error(), have the controller catch them and then throw the appropriate kind of HttpException(BadRequestException, ForbiddenException etc..)
Have the controller simply call the service/validation pipe method responsible for handling that part of business logic, and throw the appropriate HttpException.
There are pros and cons to both approaches:
This seems the right way, however, the service can return Error for different reasons, how do I know from the controller which would be the corresponding kind of HttpException to return?
Very flexible, but having Http related stuff in services just seems wrong.
I was wondering, which one (if any) is the "nest js" way of doing it?
How do you handle this matter?
Let's assume your business logic throws an EntityNotFoundError and you want to map it to a NotFoundException.
For that, you can create an Interceptor that transforms your errors:
#Injectable()
export class NotFoundInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// next.handle() is an Observable of the controller's result value
return next.handle()
.pipe(catchError(error => {
if (error instanceof EntityNotFoundError) {
throw new NotFoundException(error.message);
} else {
throw error;
}
}));
}
}
You can then use it by adding #UseInterceptors(NotFoundInterceptor) to your controller's class or methods; or even as a global interceptor for all routes. Of course, you can also map multiple errors in one interceptor.
Try it out in this codesandbox.
Nest Js provides an exception filter that handles error not handled in the application layer, so i have modified it to return 500, internal server error for exceptions that are not Http. Then logging the exception to the server, then you can know what's wrong and fix it.
import 'dotenv/config';
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, Logger } from '#nestjs/common';
#Catch()
export class HttpErrorFilter implements ExceptionFilter {
private readonly logger : Logger
constructor(){
this.logger = new Logger
}
catch(exception: Error, host: ArgumentsHost): any {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const response = ctx.getResponse();
const statusCode = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
const message = exception instanceof HttpException ? exception.message || exception.message?.error: 'Internal server error'
const devErrorResponse: any = {
statusCode,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
errorName: exception?.name,
message: exception?.message
};
const prodErrorResponse: any = {
statusCode,
message
};
this.logger.log( `request method: ${request.method} request url${request.url}`, JSON.stringify(devErrorResponse));
response.status(statusCode).json( process.env.NODE_ENV === 'development'? devErrorResponse: prodErrorResponse);
}
}
You may want to bind services not only to HTTP interface, but also for GraphQL or any other interface. So it is better to cast business-logic level exceptions from services to Http-level exceptions (BadRequestException, ForbiddenException) in controllers.
In the simpliest way it could look like
import { BadRequestException, Injectable } from '#nestjs/common';
#Injectable()
export class HttpHelperService {
async transformExceptions(action: Promise<any>): Promise<any> {
try {
return await action;
} catch (error) {
if (error.name === 'QueryFailedError') {
if (/^duplicate key value violates unique constraint/.test(error.message)) {
throw new BadRequestException(error.detail);
} else if (/violates foreign key constraint/.test(error.message)) {
throw new BadRequestException(error.detail);
} else {
throw error;
}
} else {
throw error;
}
}
}
}
and then
You could also use a factory or handler to when controller catch the exception (error or domain error) its map it to another HttpException.
#Controller('example')
export class ExampleController {
#Post('make')
async make(#Res() res, #Body() data: dataDTO): Promise<any> {
try {
//process result...
return res.status(HttpStatus.OK).json(result);
} catch (error) {
throw AppErrorHandler.createHttpException(error); //<---here is the error type mapping
};
};
};
It seems my client is not capturing the response value from the server and displaying it.
Here is my component code:
export class MyComponent implements OnInit {
data: string;
constructor(private myService: MyService) {}
ngOnInit() {}
testCall() {
this.myService.getData().subscribe(data => this.data = data);
console.log("Data: " + this.data);
}
}
The service code:
#Injectable()
export class MyService {
private url = 'http://localhost:5000/myproj/api/test';
constructor(private http: HttpClient) { }
// Get data from the server
getData(): Observable<string> {
console.log("in getData() method");
return this.http.get<string>(this.url)
.pipe(
catchError(this.handleError) // then handle the error
);
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return new ErrorObservable('Something went wrong; please try again later.');
};
}
The request goes to the server, and the server responds with the data in the response body, and a status code of 200, which you can see in developer tools in Internet Explorer:
But for some reason, when I call the service method getData(), the angular client code calls the catchError() method I defined, and prints:
Backend returned code 200, body was: [object Object]
ERROR Something went wrong; please try again later.
Why is the server returning status 200 (OK), but the Angular client is calling the catchError() method?
EDIT:
Here is my server side API code:
#RequestMapping(value = "/test", method = RequestMethod.GET, produces = "text/plain")
public String testApi(HttpServletRequest request) {
System.out.println("in /test");
String response = "my response";
return response;
}
The Response Body is not proper JSON format, hence the "Invalid character" error which is produced from the deserialization. The service is expecting properly formed JSON.
Update your API to return a valid JSON object by using "application/json" and returning an object as shown in the following post: Spring MVC - How to return simple String as JSON in Rest Controller
You need to place the console.log inside the .subscribe() method
this.myService.getData().subscribe(data => {
this.data = data;
console.log(this.data);
});
You have to set responseType to 'text' in a request options object. Here's a sample:
return this.http.get(`myApi/ExampleMethod/param`, { responseType: 'text' })
.pipe(
catchError(
this.errorHandler.handleError.bind(this)
)
);
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
})
}
}