I am new to angular. I have a json file where I can configure the url that I need to use in my app.
app/config/development.json
{
"apiUrl": "http://staging.domain.com:9000/",
"debugging": true
}
And below is my code in config.service.ts:
export class ConfigService {
private apiURL:any;
constructor (private http: Http) {}
getApiURL(){
this.http.get("app/config/development.json").map(res:Response=>res.json())
.subscribe(data=>{
this.apiURL = data;
})
console.log(this.apiURL);//this returns undefined
}
}
I want to make this.apiURL to contain the response of the http.get.
And when I create another method, the value of this.apiURL is still the same from the method getAPIURL().
someMethod()
{
console.log(this.apiURL)//this must contain the response from http.get
}
You can do something like this.
In your service file.
//whatever model u defined
getApiURL():Observable<Object[]>{
return this.http.get(this.whateverURL)
.map(res:Response=>res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
}
In your component file
yourData:Object[];
//whatever Model u defined.
//assuming yourService is your service instance which u did in constructor.
this.yourService.getApiURL()
.subscribe(
yourData=>{
this.yourData=yourData;
},err=>{
console.log(err);
alert("Something went wrong");
}
)
}
Related
I'm trying to test a service in my Angular app but I don't understand how I can mock a variable declared outside my method.
My service looks like this:
export class MyService {
private token: string
public myMethod(): Promise<boolean> {
if(!this.token) // do Something
else // do Something else
}
}
What am I missing?
Going off what Andrei Gatej said, we can make the token a getter. Now outside classes/contexts can only get the token but they cannot write to it. This will help us in mocking it in the unit tests.
Something like this:
export class MyService {
// the _ indicates this is a private variable
private _token: string;
get token(): string {
return this._token;
}
// whenever you set your token, do this._token = ....
public myMethod(): Promise<boolean> {
if(!this.token) // do Something
else // do Something else
}
}
Then in your spec file, assuming you have it set up already
it('should do the if block', async(done) => {
spyOnProperty(service, 'token', 'get').and.returnValue(null);
await service.myMethod();
await fixture.whenStable();
// the rest of your assertions
});
it('should do the else block', async(done) => {
spyOnProperty(service, 'token', 'get').and.returnValue('a token value');
await service.myMethod();
await fixture.whenStable();
// the rest of your assertions
});
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)
)
);
So I am trying to subscribe to a simple service that return data from a local JSON file.
I have managed to get the service working, I can log it out in the function, but when I subscribe to the service in the angular 2 component, it is always undefined. I'm not sure why? Any help would be much appreciated.
API service
export class ApiService {
public data: any;
constructor(private _http: Http) {
}
getData(): any {
return this._http.get('api.json').map((response: Response) => {
console.log('in response', response.json()); //This logs the Object
this.data = response.json();
return this.data;
})
.catch(this.handleError);
}
}
Component
export class AppComponent {
public data: any
public informationData;
constructor(private _api: ApiService) {}
public ngOnInit(): void {
console.log(this.getDataFromService()); // This return undefined
}
public getDataFromService() {
this._api.getData().subscribe(response => {
this.informationData = response;
return this.informationData;
});
}
}
Maybe some pictures help?
The numbers here indicate the order of operations.
Send the Http Request
Component is initialized and calls the getMovies method of the movieService.
The movieService getMovies method returns an Observable. NOT the data at this point.
The component calls subscribe on the returned Observable.
The get request is submitted to the server for processing.
The ngOnInit method is complete.
Any code here after the subscribe cannot access the movies property since the data has not yet been returned.
Receive the Http Response
At some LATER point in time ...
The movies are returned to the service.
If the process was successful, the first callback function is executed.
The local movies property is assigned to the movies returned from the service. It is only here that the movies property is finally set.
Attempting to access the movies property prior to step #8 results in an error.
Can we access the value here? NO
To fix it:
objResponse;
this.service.getData().subscribe((result: any)=> {
this.objResponse=result;
}
Returning something won't required
you can do it like this:
In your app-component:
public getDataFromService() {
this._api.getData(this);
}
public setData(data: any){
this.data=data;
}
In your service/api.ts:
public getData(obj: appComponentModel){
this.http.get(url).subscribe(res => obj.setData(res));
}
Try with:
getData(): any {
return this._http.get('api.json');
}
or
getData(): any {
return this._http.get('api.json').map((response: Response) => {
response.json();
})
You've got a problem between sync and async function. You'r issue is: getDateFromService is syncronous and the content inside is async. So when the ngOnInit function call getDataFromService, you'r code don't wait the async task. you'r getDataFromService need to return an observer or need to implement the return of your API (you need to choose).
public ngOnInit(): void {
console.log(this.getDataFromService().subscribe(data => console.log(data)); // This return undefined
}
public getDataFromService() {
return this._api.getData();
}
Instead of logging at the ngOnInit() method as you did
public ngOnInit(): void {
console.log(this.getDataFromService()); // This return undefined }
log inside the subscribe() method as
export class AppComponent {
public data: any
public informationData;
constructor(private _api: ApiService) {}
public ngOnInit(): void {
this.getDataFromService(); //don't log here, logging here will return undefined
}
public getDataFromService() {
this._api.getData().subscribe(response => {
this.informationData = response;
console.log(this.informationData); //log here, like this
return this.informationData;
});
}
}
Imagine 'subscribe' as a separate thread running, write everything that is needed inside an anonymous function inside 'subscribe'. Whenever the 'data' is available, it will be available inside the subscribe method.
Hope this helps.