Casting a type (interface) via map to observable

2019-02-26 00:21发布

问题:

I've been learning TypeScript with Angular. And currently I stuck as this moment. Previously I used subscribed method and everything works flawlessly, but not I decided to rewrite the code using async pipe and it just does't work....

  1. RestAPI retursn requests in particular format

export interface responseFormat {
  success: boolean;
  data: any;
}

  1. returned data can be in different types. In this particular case data returns restaurant object... It also might be arrays of different objects...

export interface restaurant {
  _id: string;
  name: string;
  description: string;
  images: file[];
  cuisines: cuisine[];
  blocked: boolean;
  created: Date;
  business_hours: object;
}

  1. Previously I used the following code (it works):

/* Provider */

getRestaurant(): Observable<responseFormat> {
  return this.http.get<responseFormat>(environment.api + "/restaurant");
}

/* Component */

private restaurant: restaurant;

getRestaurant() {
  this.restaurantProvider.getRestaurant().subscribe(res => {
    if (res.success) this.restaurant = res.data;
  });
}

as a result the type of the variable is assigned.... But I don't want to keep the record of subscriptions thus I wanted to convert the code using Angular async pipe... This is what I've done... it doesn't works

/* Provider */
getRestaurant(): Observable<responseFormat> {
  return this.http.get<responseFormat>(environment.api + "/restaurant");
}

/* Component */
private restaurantObservable: Observable<restaurant>;

getRestaurant() {
  this.restaurantObservable = this.restaurantProvider.getRestaurant().map(res => <restaurant>res.data);
  // or //
  this.restaurantObservable = this.restaurantProvider.getRestaurant().map(res => res.data as restaurant);
}

But the above code doesn't cast the type of the response to the interface...

What am I doing wrong???

In other words I can get the Observable variables in template (with https://angular.io/api/common/AsyncPipe> like that:

/* Provider */
getRestaurant(): Observable<responseFormat> {
  return this.http.get<responseFormat>(environment.api + "/restaurant");
}

/* Component */
private restaurantObservable: Observable<restaurant>;

getRestaurant() {
  this.restaurantObservable = this.restaurantProvider.getRestaurant().map(res => res.data as restaurant);
}

/* Template */
<div class="name">{{ (restaurantObservable | async)?.name }}</div>
// or //
<div *ngIf="restaurantObservable | async as restaurant">
  <div>{{ restaurant.name }}</div>
</div>

But I can't get the property of the restaurant observable in component... For instance this.restaurant.cuisines throw the error "property cuisines does not exist on type Observable"

回答1:

Why not directly return an Observable<restaurant> from your service?

getRestaurant(): Observable<restaurant> {
  return this.http.get<responseFormat>(environment.api + "/restaurant")
    .map((response: responseFormat) => response.data as restaurant);
}

Then on component side:

getRestaurant() {
  this.restaurantProvider.getRestaurant().subscribe((res: restaurant) => {
    this.restaurant = res;
  });
}

The error handling (success flag, HTTP errors, etc) could be handled via an HTTP Interceptor.



回答2:

The Angular async template pipe subscribes to the Observable, that is how it is able to access the restaurant object and its properties in the template.

If you want to access to these same thing in the component controller then you need to make a subscription in the component controller.



回答3:

Given that your rest service or provider returns a Restaurant in this format (i've always used classes not interfaces, sorry i can't provide any help with this distinction, so in my example Restaurant is a class).

export class Restaurant { 
    _id: string; 
    name: string; 
    description: 
    string; email: 
    { 
        address: string; 
        confirmation_code: string;
         confirmed: boolean; 
    }; 
    auth: { 
        password: string; 
    }; 
    branches: Branch[];
    menus: Menu[];
    images: File[];
    cuisines: Cuisine[];
    blocked: boolean;
    created: Date;
    business_hours: object; 
}

You should be able to have angular serialise the response for you as an example:

getRestaurant(): Observable<Restaurant> {
  return this.http.get<Restaurant>(environment.api + "/restaurant");
}

That is given you also have a also imported the other types Branch, Menu, File, Cuisine into the typescript file containing the restaurant. for example branch would look like

export class Branch {
   // branches properties
}