Before Angular 5, we imported and used Http from #angular/http. Now we use HttpClient from #angular/common/http. There's some level of convenience added, because no we don't need the unnecessary step of turning all of our response data to JSON format every time we make a call using .map(res => res.json()). However, now we aren't suppose to use .map. We are suppose to go straight to .subscribe(). Again, this is suppose to be convenient. However, I used to do some logic in my services, where my API calls live, before returning the data to the subscribing functions in other components. For example,
The component:
getData(id) {
this.service.getDataFromApi(id).subscribe(res => this.doSomething(res));
}
The service:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response.json();
return this.data;
});
}
}
Now, they suggest we shorten the service call to something like the following:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id);
}
}
I am not suppose to use .map because it will no longer be supported in the future. How can I do some logic before returning the data? I don't want to make a call for some data that already exists and doesn't change. Multiple components are using this service. Am I suppose to turn every single service call into a raw promise and run some logic before resolving? If I were to just call subscribe right on the component's function, I wouldn't even need to have the service. Am I missing something here?
You can use map. The new HttpClient has the added convenience of defaulting the response type to JSON, but it still returns an Observable -- and there are no plans to deprecate map from the Observable API.
So, your code only needs slight modification (take out the .json()):
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response;
return this.data;
});
}
}
See the Observable API docs here.
If you use the new HttpClient, there is no res.json method. It will work automatically, just pass the response type like this:
return this.http.get(this.apiPath + id)
.toPromise()
.then(data => {
console.log(data)
return data
});
Related
I am solving this issue:
The application flow:
I have to call the first API endpoint (let's call it EP-A for simplicity) which takes Blob as body and fileType as a request parameter. Its performed via calling automatically generated class
uploadFile$Response(params?: {
fileType?: 'USER_AVATAR' | 'UNKNOWN' | 'DELIVERY_LOGO' | 'PAYMENT_LOGO' | 'ITEM_PICTURE';
body?: { 'file'?: Blob }
}): Observable<StrictHttpResponse<FileUploadResponse>> {
const rb = new RequestBuilder(this.rootUrl, FileControllerService.UploadFilePath, 'post');
if (params) {
rb.query('fileType', params.fileType, {});
rb.body(params.body, 'application/json');
}
return this.http.request(rb.build({
responseType: 'blob',
accept: '*/*'
})).pipe(
filter((r: any) => r instanceof HttpResponse),
map((r: HttpResponse<any>) => {
return r as StrictHttpResponse<FileUploadResponse>;
})
);
}
The StrictHttpResponse<T> is simply an interface holding a "generic" body (so you can retrieve data that will have a structure defined by swagger from which this method is generated).
Then the result FileUploadResponse which is an object like
{
uuid: string,
time: Timestamp
...
Other members omitted for simplicity
...
}
is sent to another EP (let's call it EP-B) right after EP-A call returns a value, EP-B takes an object below as a body and currently logged person as a path variable.
{
uuid: string
}
So before calling EP-B the result from EP-A should be parsed (in this case, the uuid field should be taken and put into a new object for EP-B calling)
Again via the generated method with a similar signature as the one above (and I will omit it for simplicity).
If everything performed well, I´d like to let the caller know about that. If anything failed (any of these 2 EP calls), I´d like to let it know to call of this method to react somehow (show alert, change page somehow, ...)
The method I have is now incomplete, I do not know how to "connect" these 2 Observables, I´ve read about mergeMap, flatMap, etc. but I am not sure how to use it in my case.
updateUserAvatar(avatar: Blob): Observable<boolean> {
return new Observable<boolean>((observer) => {
// Calling EP-A
this.avatarFormChangeRequestSubscription = this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar
}
})
.subscribe((response: StrictHttpResponse<FileUploadResponse>) => {
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
},
(error: any) => {
observer.error(error);
});
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '' // the UUID from observable response above should be placed here
}
};
// Calling EP-B
this.avatarFormChangeRequestSubscription = this.userControllerService.updateUserAvatar$Response(avatarUpdateParams)
.subscribe((response: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
},
(error: any) => {
observer.error(error);
});
});
}
At the end I would like to add "use case" flow too to understand what I am trying to achieve from user view:
User uploads his photo which is firstly uploaded into a file system (and linked with database record) on BE side, then this file is linked to his profile as his profile picture.
You could do it using rxjs. Something like that might works :
this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar,
},
})
.pipe(
tap((responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Do whatever you want here, but you might not need that since you get the response below as well (in the flatMap)
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
}),
flatMap(
(responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '', // the UUID from observable response above should be placed here
},
};
return this.userControllerService.updateUserAvatar$Response(avatarUpdateParams);
}
),
tap((responseOfTheSecondApiCall: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
}),
catchError((err: any) => of(err))
)
.subscribe(); // Empty subscribe() call to trigger the http request. Not needed if you get the result somewhere else (eg if your method return an observable that you want to handle the result somewhere else)
flatMap() is the same as mergeMap. Change it as you wish, there's a lot of option like map or switchMap that you should learn about since they are useful.
Basically, the pipe allow you to chain functions, and if there is an error, then the catchError is triggered.
Tip: Note that what is in the pipe is executed BEFORE the result of your api call. So if you want to do something with your result before to get it, then think about rxjs:
service
getUser(id: string) {
return this._http.get<any>(url).pipe(
map(result => result.email), // Return only the email
);
}
component:
ngUnsubscribe = new Subject();
ngOnInit() {
this._userService.getUser(1)
.pipe(takeUntil(this.ngUnsubscribe)) // Don't forget to unsubscribe !
.subscribe(email => console.log('email = ', email))
}
ngOnDestroy() {
this.ngUnsubscribe.unsubscribe();
// or
// this.ngUnsubscribe.next();
// this.ngUnsubscribe.complete();
}
I have made an API to display the followers and following users of an user. Everything is displayed properly on the screen. But the problem is if I try to console.log() the array it is stored in after calling the method it says that it is an empty array. I'm really stuck and don't know what to do further. Here are my examples could someone help out or is experiencing the same problem please let me know.
ngOnInit:
ngOnInit(): void {
localStorage.setItem("gd", "78F88FC0-7A58-49CD-881E-4B36C5C29B71");
this.getUsers();
this.getFollowing();
this.getFollowers();
console.log("Followers: ", this.followers)
console.log("Following: ", this.following)
}
Methods:
getUsers() {
this._userService.getUsers().subscribe(res => {
this.users = res;
})
}
getFollowing() {
this._userService.getFollowing().subscribe(res => {
this.following = res;
})
}
getFollowers() {
this._userService.getFollowers().subscribe(res => {
this.followers = res;
})
}
Console:
HTML output:
I want to make an answer less generally that the link exposed
We can use forkJoin to make all the calls and get the response in an unique subscribe
ngOnInit()
{
forkJoin(this._userService.getUsers(),
this._userService.getFollowing(),
this._userService.getFollowers())
.subscribe(([users,following,followers])=>{
this.users=users
this.following=following
this.followers=followers
console.log(this.users) //<---give value
})
console.log(this.users) //<--has no value outside subscribe function
}
And yes, only has sense into subscribe function make a console.log(this.users)
Remember: the services return observables, we subscribe in components
onFetchData() {
this.http
.get('https://financialmodelingprep.com/api/v3/company/stock/list')
.pipe(
map((respData) => {
console.log('respData : ', respData);
}))
.subscribe(item => {
});
}
Need help trying to get property name from API I'm calling above, I'm using angular for the first time.
Looking to return object so I can access the properties like name or exchange.
Firstly, I would encourage you to create interfaces for your data models. They don't have to match what your API returns, but in your case it makes sense because the API response is well named.
export interface Stock {
name: string;
exchange: string;
}
Create a service that will make your API calls. The service won't invoke the http request itself, it will just return the observable. The service knows how it is going to make the call, and the component knows when the call should be made.
stock.service.ts
fetchData(): Observable<Stock[]> {
return this.http
.get('https://financialmodelingprep.com/api/v3/company/stock/list')
.pipe(
map((response: any) => response.symbolsList)
);
}
Inject the service into your component and invoke the call whenever you want to. Set a public property so that your HTML can "see" it.
component.ts
export class MyComponent {
constructor(private stockService: StockService) { }
stocks: Stock[];
onFetchData() {
this.stockService.fetchData().subscribe((stocks: Stock[]) => {
this.stocks = stocks;
});
}
}
Then bind to the data in your HTML:
component.html
<div *ngFor="let stock of stocks">
{{stock.name}}: {{stock.exchange}}
</div>
DEMO: https://stackblitz.com/edit/angular-waymkr
You are returning nothing in map function so you cannot get any data.
Don't use map. Then you will get an object that has symbolsList property.
onFetchData() {
this.http
.get('https://financialmodelingprep.com/api/v3/company/stock/list')
.subscribe(item => {
console.log('respData : ', item);
});
}
So in normal javascript if I wanted to assign a value to a variable and then use that value outside of a function it would be done by declaring the variable first and then define it's value in the function. I'm brand new to typescript and angular so I am missing how to do this.
In the code below I am trying to get the value from a method in a service and then pass that value into my return. (I hope that makes sense). However I keep getting undefined on console.log(url) with no other errors.
emailsAPI() {
let url: any
this.apiUrlsService.urlsAPI().subscribe(
data => {
this.results = data
url = this.results.emails
}
);
console.log(url)
return this.http.get('assets/api/email_list.json')
}
api-urls service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
#Injectable()
export class ApiUrlsService {
constructor(
private http: HttpClient
) { }
urlsAPI () {
return this.http.get('assets/api/api_urls.json')
}
}
That's because you're calling async method subscribe and then trying to log the coming value before subscription is resolved. Put last two statements (console.log and return) inside the curly braces just after assigning this.results.emails to the url variable
emailsAPI(): Observable<any> {
let url: any
return this.apiUrlsService.urlsAPI()
.flatMap(data => {
this.results = data
url = this.results.emails
// you can now access url variable
return this.http.get('assets/api/email_list.json')
});
}
As per reactive programming, this is the expected behaviour you are getting. As subscribe method is async due to which you are getting result later on when data is received. But your console log is called in sync thread so it called as soon as you are defining subscribe method. If you want the console to get printed. Put it inside your subscribe data block.
UPDATE:
As per your requirement, you should return Subject instead of Observable as Subject being data consumer as well as data producer. So it will consume data from httpget request from email and act as a producer in the method from where you called emailsAPI method.
emailsAPI(): Subject<any> {
let emailSubject:Subject = new Subject();
this.apiUrlsService.urlsAPI()
.flatMap(data => {
this.results = data
return this.results.emails;
}).
subscribe(url=> {
this.http.get(your_email_url_from_url_received).subscribe(emailSubject);
});
return emailSubject;
}
The subject can be subscribed same as you will be doing with Observable in your calee method.
I'm trying to learn Angular 2 and am rebuilding an Angular 1 app I've made with Angular 2 using the Angular CLI. I've setup a HTTP GET request, which fires successfully, and setup a subscriber to interpret the result, and console logging in the subscriber function shows the data I expect. However, no data is being updated on the template.
I tried setting the data to an initial value, to a value in the ngOnInit, and in the subscriber function, and the initial and ngOnInit update the template accordingly. For the life of me, I can't figure out why the template won't update on the subscribe.
events: any[] = ['asdf'];
constructor(private http: Http) {
}
ngOnInit() {
this.events = ['house'];
this.getEvents().subscribe(this.processEvents);
}
getEvents(): Observable<Event[]> {
let params: URLSearchParams = new URLSearchParams();
params.set('types', this.filters.types.join(','));
params.set('dates', this.filters.dates.join(','));
return this.http
.get('//api.dexcon.local/getEvents.php', { search: params })
.map((response: Response) => {
return response.json().events;
});
}
processEvents(data: Event[]) {
this.events = ['car','bike'];
console.log(this.events);
}
The data is being displayed via an ngFor, but car and bike never show. Where have I gone wrong?
You have gone wrong with not respecting the this context of TypeScript, if you do stuff like this:
.subscribe(this.processEvents);
the context get lost onto the processEvents function.
You have to either bind it:
.subscribe(this.processEvents.bind(this));
Use an anonymous function:
.subscribe((data: Events) => {this.processEvents(data)});
Or set your method to a class property:
processEvents: Function = (data: Event[]) => {
this.events = ['car','bike'];
console.log(this.events);
}
Pick your favourite, but I like the last option, because when you use eventListeners you can easily detach them with this method.
Not really sure with what's going on with that processEvents. If you want to subscribe to your response just do:
this.getEvents()
.subscribe(data => {
this.events = data;
});