39

Edit: It looks like my main problem now is that I can't seem to display async data from an object. I have a promise containing the data object, and when I use

{{ data | async }}

it will display

[object Object]

The issue is, I want to be able to display all the different attributes; i.e, Name, Symbol, etc. In Angular 1, I would just use

{{ data.Name | async }}

but that doesn't work here, since the async pipe tries to resolve the data.Name promise, which doesn't exist. I want to resolve the data promise and then display the Name key from it. At the moment, I'm working on creating my own pipe to display a key from an async object, but I'm wondering if there's a built-in Angular 2 pipe or function to handle this!


I've created a StockService class that returns a Promise containing an object to my StockInfo class, which contains the HTML to be displayed. I want to display the name of this object in my HTML, but I can't seem to get it to display.

In my StockInfo constructor:

this.stock.getStockData(this.ticker, http).then(function(val) {
  this.data = val;

  this.name = new Promise<string>(function(resolve) {
    resolve(this.data.Name);
  });
});

where this.stock is the StockService object.

In my HTML:

<h2>{{name | async}}</h2>

I've tried a number of different arrangements before settling on this one. I want the StockService class to handle the data fetching and the StockInfo class to handle the display. In Angular 1, I would create a factory for fetching data and handle the data processing in the controller, but I'm not quite sure how to go about this in Angular 2.

Is there a way to get it to display, or are there better ways to design my code that I should look into? Thanks!

user2884505
  • 525
  • 1
  • 6
  • 19

7 Answers7

83

You do not need any special pipe. Angular 2 suppport optional field. You just need to add ? in your object

{{ (data | async)?.name }}

or

{{(name | async)?}}
Jecfish
  • 4,026
  • 1
  • 18
  • 16
  • Does `| async` work on promises? I thought it only worked on Observables? – Pylinux Jul 12 '16 at 06:31
  • 2
    `async` pipe accept either promise or observables. It's mention in the official Angular 2 documentation here. https://angular.io/docs/ts/latest/guide/pipes.html – Jecfish Jul 13 '16 at 22:24
  • 1
    "The safe navigation operator `?` means that the field is optional and if undefined, the rest of the expression should be ignored" – from https://angular.io/cheatsheet – M165437 Jan 04 '17 at 21:19
19

There's nothing wrong with the accepted answer above. But it becomes a hassle to append | async? when we need to display many properties of the object. The more convenient solution is as follows:

<div *ngIf="data | async as localData">
   <div> {{ localData.name }} </div>
   <div> {{ localData.property1 }} </div>
   <div> {{ localData.property2 }} </div>
</div>
Manoj Shrestha
  • 4,246
  • 5
  • 47
  • 67
9

You can also use pluck from rxjs/observable:

{{ user.pluck("name") | async }}

Pluck Returns an Observable containing the value of a specified nested property from all elements in the Observable sequence. If a property can't be resolved, it will return undefined for that value.

Bruno João
  • 5,105
  • 2
  • 21
  • 26
pleerock
  • 18,322
  • 16
  • 103
  • 128
4

If you work with Observable you can display data like this way:

<div *ngIf="data | async; let _data">
   <h3>{{_data.name}}</h3>
</div>

or

<h3>{{(data | async).name}}</h3>
Dmitry Grinko
  • 13,806
  • 14
  • 62
  • 86
2

I think you are making this too complex, and just need to do something like this.

this.name = 
  this.stock.getStockData(this.ticker, http)
  .then( val => val.Name )

and

<h2>{{name.Name | async}}</h2>
Simon H
  • 20,332
  • 14
  • 71
  • 128
  • Thanks! This certainly makes things a lot simpler and I can't believe I didn't think to write it this way before, but the data still isn't displaying in the HTML. I've checked to make sure that getStockData is returning a Promise with the correct data, but I still can't get it to show up in the HTML. – user2884505 Dec 18 '15 at 20:19
  • are you certain that `getStockData` is calling `resolve`? – Simon H Dec 18 '15 at 20:21
  • Yep; when I added a console.log to the monstrosity in my OP, the resulting value had all the data I wanted. – user2884505 Dec 18 '15 at 20:24
  • Interestingly enough, when I change the HTML to

    {{name | async}}

    , it will display [object Object], but trying to dereference the Name key leads to nothing showing.
    – user2884505 Dec 18 '15 at 20:48
  • Thanks Simon! This works, but there are maybe 20 key-value pairs in this Object, and I want to display all 20 of these in the page. The getStockData method makes a HTTP GET request, and I want to be able to get all this data with a single call to getStockData. Is there no way to store the results in an object? – user2884505 Dec 18 '15 at 21:04
2

So I ended up writing my own asynchronous key pipe. Huge thanks to Simon for helping guide me here.

import {Pipe} from 'angular2/core';

@Pipe({
    name: 'key',
    pure: false
})

export class KeyPipe {
    private fetchedPromise: Promise<Object>;
    private result: string;

    transform(value: Promise<Object>, args: string[]) {
        if(!this.fetchedPromise) {
            this.fetchedPromise = value
                .then((obj) => this.result = obj[args[0]] );
        }
        return this.result;
    }
}

Usage:

<h2>{{ data | key: 'Name' }}</h2>

Someone please comment if Angular has its own functions for resolving a key from an asynchronous object.

user2884505
  • 525
  • 1
  • 6
  • 19
2

The OP asked for promises but in case people are using Observables, adapting @user2884505's answer, since pluck isn't directly available on observables as a method in recent versions of RxJS, you may have something like this :

import { Pipe, PipeTransform } from '@angular/core';

import { Observable } from 'rxjs';
import { pluck } from 'rxjs/operators';

@Pipe({
  name: 'asyncKey',
  pure: false
})
export class AsyncKeyPipe implements PipeTransform {
  private observable: Observable<Object>;
  private result: Object;

  transform(value: any, ...args: any[]): any {

    if (!this.observable) {
      this.observable = value.pipe(pluck(...args));
      this.observable.subscribe(r => this.result = r);
    }

    return this.result;
  }
}

And then, you can use it, even for nested keys :

{{ user$ | asyncKey: 'address' : 'street' }}
Robin
  • 1,438
  • 2
  • 19
  • 29