I have a service class that should call an api and return the results:
import {Component, View} from 'angular2/angular2';
import { Inject} from 'angular2/di';
import {Http} from 'angular2/http';
var httpMap = new WeakMap<ScheduleService, Http>();
export class ScheduleService {
meetings: Array<any>;
http: any;
constructor(#Inject(Http) http:Http){
httpMap.set(this, http);
this.http = http;
//http.get('/api/sample')
// .map(response => response.json())
// .subscribe(data => {
// this.serverData = data;
// });
}
getMeetings(){
var path = '/api/meetings/';
return httpMap.get(this).get(path);
}
}
The service class is being called and injected correctly. The issue I am having is that when ever I call the getMeetings method it never makes the request to /api/meetings. If you guys notice in the constructor there is a get request to /api/sample that works perfectly if I uncomment it and run the program I check my network tab and I can see the request was made.
Looks like http.get(path) doesn't send off a request unless you call http.get(path).subscribe(); even if you don't do anything with the response.
Plunker: https://plnkr.co/edit/b1fxoIUy31EHTC84U344?p=preview
In the Plunker, open the network view in the console and click the With Subscribe / Without Subscribe buttons for comparison.
Related
im building an app and i need alittle bit of guidance in terms of app structure and logic
ill greatly appreciate any help!
so in the app i am making a server call and i fetch documents from there(parcels),
now i want to minimize the amount of calls as much as possible because i assume it will improve my app performance.
am i right? im going to fetch all documents and then do any filtering/splice or whatever needed in the client side every time a parcel was deleted and etc, i tried server side handling(so server side deleting a parcel for example and returns the updated parcels array after the delete) but it was pretty slow because the parcels array is quite large, and it makes a call to mongoDB so it also takes time(specially the non "onstock" one).
so my idea was to make the api call in a service as soon as it is initialized and store the parcels (and also store another array of only the parcels that are onstock) in subjects.
but i have abit of a problem,
i dont know how to display errors/loading screen for proper user experience because my api call is in a service,
so i tired to make a subject representing the loading state(i use it in a component to display a loading spinner) but now i also need a subject representing the error state(if the api call has an error i want to display it to the user) and it becomes cumbersome,
2.in the service there are going to be more methods and they are going to have to manipulate the parcels subjects aswell so i wonder if i should subscribe in a top level component and drill the subjects inside to sub component or can i just subscribe many times in sub components and it wont affect performance?
sorry for the long post as im lacking the best practice knowledge.
the service code:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { BehaviorSubject} from 'rxjs';
import { Parcel } from 'src/app/models/Parcel.model';
#Injectable({
providedIn: 'root',
})
export class ParcelsService {
apiUrl: string = 'http://localhost:5000';
allParcels$ = new BehaviorSubject<Parcel[]>([]);
stockParcels$ = new BehaviorSubject<Parcel[]>([]);
isLoading$ = new BehaviorSubject<boolean>(true);
constructor(private http: HttpClient) {
this.http.get<Parcel[]>(`${this.apiUrl}/parcels`).subscribe((response) => {
this.allParcels$.next(response);
this.stockParcels$.next(
response.filter((parcel) => parcel.isOnStock === true)
);
});
this.isLoading$.next(false)
}
}
the only component currently that uses the subjects (there will be more)
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ParcelsService } from 'src/app/services/parcels/parcels.service';
import { Parcel } from 'src/app/models/Parcel.model';
import { Subject, Subscription, takeUntil } from 'rxjs';
#Component({
selector: 'app-parcels-management-page',
templateUrl: './parcels-management-page.component.html',
styleUrls: ['./parcels-management-page.component.css'],
})
export class ParcelsManagementPageComponent implements OnInit, OnDestroy {
private ngUnsubscribe = new Subject<void>();
isFetching = true;
allParcels: Parcel[] = [];
stockParcel: Parcel[] = [];
constructor(private parcelsService: ParcelsService) {}
ngOnInit(): void {
this.parcelsService.isLoading$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((response) => {
this.isFetching = response;
console.log(this.isFetching);
});
this.parcelsService.allParcels$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((response) => {
this.allParcels = response;
console.log(this.allParcels);
});
this.parcelsService.stockParcels$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((response) => {
this.stockParcel = response;
console.log(this.stockParcel);
});
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
I need to make a HTTP call in my test and I need to verify my observeable.
When I debug the code, the HTTP call is getting called in service, but in my test, it fails and it says http is undefined, but while debugging I'm able to see the http observal in console.
//service.ts file
//imports modles goes here
export class DataService {
private api = this.configService.config.apiUrl;
constructor(private http: HttpClient,
private configService: AppConfigService,
private errorService: ErrorService)
{
}
public getCustomerList(): Observable<IUserResponse> {
return this.http.get<IUserResponse>`${this.api}/v1/getusers/users`);
}
}
my test file serviec.specs.ts
describe('OnboardingService', () => {
beforeEach(() => {
const httpClient = new HttpClient(new HttpXhrBackend({ build: () => new XMLHttpRequest() }));
appConfigService= new AppConfigService(httpClient);
appConfigService.config = { apiUrl:"https://test-test-getuser.json"}
erroService = new ErrorService();
service = new Dataservice(httpClient, appConfigService,erroService);
it('should return an Observable<Iuser[]>', done => {
service.getCustomerList().subscribe(response => {
expect(response.users).toBeGreaterThan(0);
done();
});
});
})
Try this
this.http.get(`${this.apiurl}/v1/getuser`).subscribe((response)=>{console.log(response.user)})
Grey right arrow shows your code to execute. Left grey arrow below shows the result of execution. Console log has no arrow in dev tools.
The result of execution .subscribe() is a Subscriber as you can notice in your screenshot. Your code just doesn't execute function you passed to .subscribe().
So your problem is with your endpoint probably. Check the network tab in your dev tools.
I'm currently working on a register page and therefore, I need to post my data to the server. The client-side validation and server-validation works. I know I can handle the client-side errors like *ngIf="(emailAddress.errors?.required || emailAddress.errors?.email) && emailAddress.touched". But how to handle the server-side errors?
In my service, if an error occurs, I simply give it into the component by return throwError(error);
But how can I know to display the specific error in my component if for example there is already someone with this email address? And how do distinguish between email/password validation errors server side?
Depends on the complexity and modules you have.
If you use any kind of state management library such as ngrx or ngxs, I suggest you do as follow:
Define State with a property 'error' which keeps track of the latest server-error.
Api call executed via actions and error is caught and stored to state also via actions. (Do whatever error mapping before saving the error to your State)
Use selectors for component to receive error stream from State.
If you don't have any state management library, you can create a BehaviorSubject within singleton service, and use it to publish server-error as soon as you got into any catchError context.
This way you can write your own http interceptor and have your response inside your component. Also in your component you know your http response is success or error.
You can implement other Http calls(GET, PUT, etc) and also handle your general errors in handleError() function.
app.module.ts
export class AppModule {
constructor() {}
imports: [
HttpClientModule,
],
providers: [
{
provide: Http,
useFactory: httpFactory,
deps: [HttpHandler]
},
],
}
export function httpFactory(httpHandler: HttpHandler) {
return new Http(httpHandler);
}
http.service.ts
export class Http extends HttpClient {
constructor(handler: HttpHandler) {
super(handler);
}
postCall(url: string, body, options?): Observable<any> {
return super.post(url, body, options)
.pipe(
tap(response => { console.log(response) }),
catchError(this.handleError(error)));
}
private handleError<T>(result?: T) {
return (error: any): Observable<T> => {
console.log(error);
return of(result as T);
};
}
}
your.component.ts
export class YourComponent {
constructor(private http: Http) {}
this.http.postCall('URL', {}).subscribe(response => {
if (response instanceof HttpErrorResponse) {
// Your Response is error
} else {
// Your Response is your desired result
}
}
I am trying to test an angular2 application. I have a login form, which uses an observable to send data to the backend:
doLogin() {
this.usersService.login(this.model)
.subscribe((data) => {
console.log("In observable: " + data.isSuccess);
if (!data.isSuccess) {
this.alerts.push({});
}
});
}
In tests I am adding a spy on the service function, which returns observable, so that component can work on it:
usersService.login.and.returnValue(Observable.of(
<LoginResponse>{
isSuccess: true
}));
When everything is ready, I dispatch an event on submit button, which triggers doLogin function in component:
submitButton.dispatchEvent(new Event("click"));
fixture.detectChanges();
It works correctly. Unfortunately, when I check if usersService.login has been called in the test:
expect(usersService.login).toHaveBeenCalled();
I get an error, because the observable didn't finish and login has not been called yet.
How should I make sure, I check my spy after observable has finished?
I don't know how you configure the service on the component but it works for me when I override providers of the component created from TestComponentBuilder.
Let's take a sample. I have a service that returns a list of string:
import {Observable} from 'rxjs/Rx';
export class MyService {
getDogs() {
return Observable.of([ 's1', 's2', ... ]);
}
}
A component uses this service to display a list asynchronously when clicking a button:
#Component({
selector: 'my-list',
providers: [MyService],
template: `
<ul><li *ngFor="#item of items">{{ item }}</li></ul>
<div id="test" (click)="test()">Test</div>
`
})
export class MyList implements OnInit {
items:Array<string>;
service:MyService;
constructor(private service:MyService) {
}
test() {
this.service.getDogs().subscribe(
(dogs) => {
this.items = dogs;
});
}
}
I want to test that when I click on the "Test" button, the test method of the component is called and the getDogs method of the service is indirectly called.
For this, I create a test that instantiate directly the service and load the component using TestComponentBuilder. In this case, I need to call the overrideProviders method on it before calling createAsync. This way, you will be able to provide your spied service to be notified of the call. Here is a sample:
let service:MyService = new MyService();
beforeEach(() => {
spyOn(service, 'getDogs').and.returnValue(Observable.of(
['dog1', 'dog2', 'dog3']));
});
it('should test get dogs', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideProviders(MyList, [provide(MyService, { useValue: service })])
.createAsync(MyList).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
}));
Edit
Since the event is triggered asynchronously, you could consider to use fakeAsync. The latter allows you to completly control when asynchronous processing are handled and turn asynchronous things in to synchronous ones.
You could wrap your test processing into
fakeAsync((): void => {
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
For more details, you could have a look at this question:
Does fakeAsync guarantee promise completion after tick/flushMicroservice
I have service defined in Angular 2 like this:
import { Inject } from 'angular2/angular2';
import { Http ,Headers , HTTP_PROVIDERS } from 'angular2/http';
export interface CourseInterface {
courseId: number,
coursePrice: number,
authorName: string
}
export class CourseDetailsService {
http: Http;
constructor(#Inject(Http) Http) {
console.log(Http)
this.http = Http;
}
load() {
console.log("came here in service")
var headers = new Headers();
headers.append('Authorization', <my username password>);
this.http.get('https://some.api',{
headers : headers
}).map(res => console.log("Response came!!!"))
console.log("done . . .")
}
}
and in another component, I use this service like this:
import {CourseInterface, CourseDetailsService} from '../services/course';
#Component({
selector: 'dashboard',
viewBindings: [CourseDetailsService]
})
#View({
template: `
<h1>Dashboard page laoded</h1>
`
})
export class Dashboard {
constructor(service: CourseDetailsService) {
service.load();
}
}
and while running the application, I can see my Dashboard component gets displayed on the screen. But however from the CourseDetailsService, no http calls are getting fired.
But in the console I could able to see the following printed:
came here in service
done . . . .
But in my chrome networks tab, I couldn't able to see any request fired to the specified url. Where I am making mistake?
I'm using Angular 2 Alpha 47
Basically the part that triggers the request itself it's the subscribe, so to make it work you have to add it.
// Service
load() {
var headers = new Headers();
headers.append('Authorization', <my username password>);
return this.http.get('https://some.api',{
headers : headers
}).map(res => console.log("Response came!!!"))
}
// Component
// 'subscribe' triggers the request!
service.load().subscribe((result) => /* do something with result */);