Reputation: 832
I'm wondering if should I send request from the angular service? or should I send it directly from the component?
First Approach:
RestaurantService.ts
getRestaurants(): Promise<_Restaurant[]> {
return this.get("/restaurants").toPromise();
};
Restaurants.component.ts
loadRestaurants = async () => {
try {
this.restaurants = await this.restaurantService.getRestaurants();
} catch (exception) {
console.log(JSON.stringify(exception, null, 2));
}
}
Which mean the request was fired through the component.
Second Approach:
RestaurantService.ts
async getRestaurants(): Promise<_Restaurant[]> {
try {
const response: _Restaurant[] = await this.get("/restaurants").toPromise() as _Restaurant[];
return response;
} catch (exception) {
throw new Error(exception);
}
};
Restaurants.component.ts
loadRestaurants = async () => {
try {
this.restaurants = await this.restaurantService.getRestaurants();
} catch (exception) {
console.log(JSON.stringify(exception, null, 2));
}
}
Which mean the request was fired from the service then return the response as a promise
So what is the best approach? if it is the second approach, it is possible to catch the error from the service and throw it to the component?
Upvotes: 0
Views: 51
Reputation: 11545
Well as Angular doc says it is better to have that logic in the service, have a look on this:
class Service {
constructor(public http: HttpClient) { }
getRestaurants(): Observable<Restaurant> {
return this.http.get<{ /* Specify HTTP response schema */ }>(url).pipe(
// Transformation to actual Restaurants at one place
map(data => data.map(restaurant => new Restaurant()),
// Handle error
catchError(err => {
logError(err);
throw err;
// Or...
return of([]); // Mock data not to crash app
}),
// If multiple subscription are made to same source, it won't do multiple http calls
shareReply(1),
);
}
}
class Component {
restaurants: Restaurant[] = [];
ngOnInit(): void {
// Prefered way
this.restaurants$ = this.service.getRestaurants().pipe(
// If, you pass error down, you'll be able to hendle it here...
catchError(err => {
return of([]);
}),
);
// Alternative
this.cleanUp = this.service.getRestaurants().subscribe(restaurants => {
this.restaurants = restaurants;
});
}
ngOnDestroy(): void {
this.cleanUp.unsubscribe();
}
}
HTML
<!-- Observable -->
<div *ngFor="let restaurant of restaurants$ | async">
{{restaurant | json}}
</div>
<!-- Non-Observable -->
<div *ngFor="let restaurant of restaurants">
{{restaurant | json}}
</div>
I have switched your code from promise to observables because observables are one of biggest benefit os using Angular. Observables can be cancelled, are nicely readable in templates, and many others that I may think of one day.
Observables are very strong, you can have always fresh information based on other observable. Have a look, it may give you some ideas...
interface ApiResponse<type> {
awaitingNewValues: boolean;
error: null | any;
response: type;
}
class Service {
currentRestaurantID = new BehaviourSubject(1);
currentRestaurantInfo: Observable<ApiResponse<Restaurant>>;
constructor(private http: HTTPClient) {
let latestRestaurants: ApiResponse<Restaurant | undefined> = {
awaitingNewValues: true,
error: null,
response: [],
};
currentRestaurantInfo = this.currentRestaurantID.pipe(
switchMap(restaurantID => {
return concat(
// This will notify UI that we are requesting new values
of(Object.assign({}, latestRestaurants, { awaitingNewValues: true })),
// The actual call to API
this.http.get(`${apiUrl}/${restaurantID}`).pipe(
// Wrap response in metadata
map(restaurant => {
return {
awaitingNewValues: false,
error: null,
response: restaurant,
}
}),
// Notify UI of error & pass error
catchError(err => {
return of({
awaitingNewValues: true,
error: err,
response: undefined,
});
}),
),
);
}),
// Save last response to be used in next api call
tap(restaurants => this.latestRestaurants = restaurants),
// Prevent calling API too many times
shareReplay(1),
);
}
}
Upvotes: 1
Reputation: 15212
Best practice is to use Observable
RestaurantService.ts
getRestaurants(): Observable<_Restaurant[]> {
return this.get('/restaurants');
};
Restaurants.component.ts
import { Subscription } from 'rxjs';
sub: Subscription;
loadRestaurants(): void {
this.sub = this.restaurantService.getRestaurants()
.subscribe(result => {
this.restaurants = result;
}, exception => {
console.log(exception);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
If you need to modify response you should use the pipe
approach in your service.
Upvotes: 0