13

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"

zgue
  • 3,793
  • 9
  • 34
  • 39
Timothy
  • 3,213
  • 2
  • 21
  • 34
  • What does the restaurant object look like? If you create a class that has the same fields as the resturant object, and use observable as an example angular will serialise the response into an object of type resturant – ConsultingEasy Apr 03 '18 at 10:04
  • Restaurant is a huge object... Will update post shortly – Timothy Apr 03 '18 at 10:06
  • Sorry if I have miss understood your question does my answer provide the kind of thing your looking for? – ConsultingEasy Apr 03 '18 at 10:14

3 Answers3

23

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.

Jeto
  • 14,596
  • 2
  • 32
  • 46
  • I am using Interceptors for handling errors. But the question here is.... If I am subscribing to Observable - the type is applied properly, but if am not subscribing (this.restaurant = this.restaurantProvider.getRestaurant();) further in code if I want to get the property of restaurant object (ex this.restaurant.cuisines) I am getting the error (property cuisines does not exist on type Observable) – Timothy Apr 03 '18 at 10:33
  • 3
    @Timothy Why would you not subscribe? You can't access an observable's data without subscribing to it. With the above code, `this.restaurant` is an actual `restaurant` instance that you can use to get `this.restaurant.cuisines`. – Jeto Apr 03 '18 at 10:40
  • This is the core of the question. I can get the properties in the template via https://angular.io/api/common/AsyncPipe – Timothy Apr 03 '18 at 10:46
  • amended the post. please have a look – Timothy Apr 03 '18 at 10:59
  • After some further searches I think I found the answer... as you and @Sammi said there is no way I can get observables object properties without subscription... Entire idea of mine is to get properly manage subscriptions.. I think this is the best answer I found so far https://stackoverflow.com/a/42514369/9590251 – Timothy Apr 03 '18 at 11:30
  • I'm incredibly new to this, but for me let testVal : Observable> = this.http .get(AppConstant.URL_GET_HUB_PORTFOLIO_SUMMARY_TILE_DATA, this.httpOptions); return testVal.map((response: responseFormat) => response.data as HubPortfolioSummaryTileData); // does not compile because map is not a property of Observable> – Woodsman Oct 11 '19 at 00:56
  • In this solution if the restaurant response has let's say one more property on it then the type assertion at the end does also include this new property which itself does not contain on the Interface .... How can we get just the property that the interface has ? – Biswas Khayargoli Aug 02 '20 at 15:11
1

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.

Sámal Rasmussen
  • 2,887
  • 35
  • 36
  • In other words there is no way I can get rid of subscriptions in the component to be able to operate Observable object properties... Correct? – Timothy Apr 03 '18 at 11:19
  • If you want to make an observable produce values that you can access, then you have to subscribe to it. Sure you can first use .filter and .map and whatever other chainable operator on the observable where you also can access the values, but you won't actually get any values unless you at some point down the line add a call to .subscribe because it is the subscribe that tells the observable to start sending values down this chain. – Sámal Rasmussen Apr 03 '18 at 11:29
  • Also if you're just starting out with working with observables then I strongly recommend that you sit down and read this whole introduction to observables by Andre Staltz first: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 – Sámal Rasmussen Apr 03 '18 at 11:32
  • Got it, I will indeed. Thanks a lot @Sammi – Timothy Apr 03 '18 at 11:35
0

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
}
ConsultingEasy
  • 385
  • 2
  • 5
  • 11
  • Not really... getRestaurant returns not a restaurant object only but a responseFormat object. like {success: true; data: restaurant;} so its like Observable> – Timothy Apr 03 '18 at 10:36
  • What puzzles me in that in further in code I wanted to get a value of particular property ,ex. this.restaurant.cuisines and I am getting following error "property cuisines does not exist on type Observable" – Timothy Apr 03 '18 at 10:38
  • Following Jetos answer when you attempt to access one of restaurants properties it would be a Restaurant object rather than an observable I think what would be beneficial is to read angulars Http guide https://angular.io/tutorial/toh-pt6, it will walk you through handling errors with pipes which might help shed some light on the issues your having. – ConsultingEasy Apr 03 '18 at 10:44
  • probably I have to change the question to "How to access the property of Observable in the component... because in template there is async pipe... but how in a component..." – Timothy Apr 03 '18 at 11:17