Aurelia CLI and RxJS - Async binding

This is the second article of the blog series about best practices with an Aurelia CLI TypeScript app and RxJS. The first article focused on integrating a minimal version of RxJS with a new Aurelia CLI TypeScript app.

This second article is about handling async observables directly within your template.

You can find the resulting app over at this github repository which will be used for further examples and articles. This tag marks the snapshot of features of this article.

Consuming Observables in Templates

Looking back at our first article we've seen how to add RxJS to an Aurelia application and work with Observables in our ViewModels. Well, that's great but often times all we need is actually just displaying streamed results over time.

Imagine we'd like to stream SPAFramework objects and access their label in our template.

import { Observable } from 'rxjs/Observable';  
import "rxjs/add/observable/of";

interface SPAFramework {  
  label: string;
  url: string;
}
export class App {  
  public frameworks: Observable<SPAFramework[]>;

  constructor() {
    const data: SPAFramework[] = [
      { label: "Aurelia", url: "http://aurelia.io" },
      { label: "Angular v4", url: "http://angular.io" },
      { label: "React", url: "https://facebook.github.io/react/" },
    ];

    this.frameworks = Observable.of(data);
  }

  ...
}

Now if we were about to iterate through all frameworks like this

<ul>  
  <li repeat.for="framework of frameworks">
    <a href="${framework.url}">${framework.label}</a>
  </li>
</ul>  

we'd get an error saying Error: Value for 'frameworks' is non-repeatable, which makes sense as Aurelia by default has no clue how to wait and iterate through the results of the Observable stream.

So in order to teach Aurelia what to do with the source, we can leverage an async binding behavior and add it to our repeated list

<ul>  
  <li repeat.for="framework of frameworks & async">
    <a href="${framework.url}">${framework.label}</a>
  </li>
</ul>  

Visit the repository for information on how to install and configure the plugin

Plucking values from complex objects

If we want to use the async binding inside an interpolation expression we can also provide the property to be plucked, so that we render only the value of a single property versus the whole object.

Imagine the scenario where we stream the SPAFramework over time

import { Observable } from 'rxjs/Observable';  
import "rxjs/add/operator/map";  
import "rxjs/add/operator/take";  
import "rxjs/add/observable/interval";  
import "rxjs/add/observable/of";  
import "rxjs/add/observable/from";

interface SPAFramework {  
  label: string;
  url: string;
}
export class App {  
  public frameworks: Observable<SPAFramework[]>;
  public frameworkOverTime: Observable<SPAFramework>;

  constructor() {
    const data: SPAFramework[] = [
      { label: "Aurelia", url: "http://aurelia.io" },
      { label: "Angular v4", url: "http://angular.io" },
      { label: "React", url: "https://facebook.github.io/react/" },
    ];

    this.frameworks = Observable.of(data);

    this.frameworkOverTime = Observable.interval(2000)
      .map((idx) => data[idx])
      .take(data.length);
  }
}

Now all that's needed is to call the async binding with additional options and specify the property to render. If it's deeply nested you can specify the path via dots.

<h1>Frameworks streamed (plucked property)</h1>  
<div>  
  ${frameworkOverTime & async: { property: 'label' }}
</div>  

Completion handler

Last but not least it might be interesting to specify a completion handler, the third argument of an Observable.subscribe call. Say we'd like to show an alternative text, once all SPAFrameworks are streamed, indicated by a property isSequenceDone.

...
export class App {  
  public frameworks: Observable<SPAFramework[]>;
  public frameworkOverTime: Observable<SPAFramework>;
  public isSequenceDone: boolean = false;

  constructor() {
    ...
  }

  public completedHandler = () => {
    setTimeout(() => this.isSequenceDone = true, 2000);
  }
}

We can do so by providing the completed option to the async binding, specifying the handler to be executed once the stream has completed:

<h1>Frameworks streamed (with binding, completed handler)</h1>  
<div with.bind="frameworkOverTime & async: { completed: completedHandler }">  
  <a if.bind="!isSequenceDone" href="${url}">${label}</a>
  <span if.bind="isSequenceDone">Sequence is done!</span>
</div>  

Conclusion

The async-binding should help to consume observables and promises directly from within your templates. No more need to subscribe and store the results to an arbitrary VM property just to be able to render it.
Looking forward to any feedback either as a comment or over at the plugins GitHub repository

photo credit: Sundine: Chain with a spider web via Pixabay (license)