Khaled Ramadan
Khaled Ramadan

Reputation: 832

Angular - It is sending requests from the service better than from the component?

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

Answers (2)

Akxe
Akxe

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

Dmitry Grinko
Dmitry Grinko

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

Related Questions