125

I am trying to understand how to use Observables in Angular 2. I have this service:

import {Injectable, EventEmitter, ViewChild} from '@angular/core';
import {Observable} from "rxjs/Observable";
import {Subject} from "rxjs/Subject";
import {BehaviorSubject} from "rxjs/Rx";
import {Availabilities} from './availabilities-interface'

@Injectable()
export class AppointmentChoiceStore {
    public _appointmentChoices: BehaviorSubject<Availabilities> = new BehaviorSubject<Availabilities>({"availabilities": [''], "length": 0})

    constructor() {}

    getAppointments() {
        return this.asObservable(this._appointmentChoices)
    }
    asObservable(subject: Subject<any>) {
        return new Observable(fn => subject.subscribe(fn));
    }
}

This BehaviorSubject is pushed new values as so from another service:

that._appointmentChoiceStore._appointmentChoices.next(parseObject)

I subscribe to it in the form of an observable in the component I want to display it in:

import {Component, OnInit, AfterViewInit} from '@angular/core'
import {AppointmentChoiceStore} from '../shared/appointment-choice-service'
import {Observable} from 'rxjs/Observable'
import {Subject} from 'rxjs/Subject'
import {BehaviorSubject} from "rxjs/Rx";
import {Availabilities} from '../shared/availabilities-interface'


declare const moment: any

@Component({
    selector: 'my-appointment-choice',
    template: require('./appointmentchoice-template.html'),
    styles: [require('./appointmentchoice-style.css')],
    pipes: [CustomPipe]
})

export class AppointmentChoiceComponent implements OnInit, AfterViewInit {
    private _nextFourAppointments: Observable<string[]>
    
    constructor(private _appointmentChoiceStore: AppointmentChoiceStore) {
        this._appointmentChoiceStore.getAppointments().subscribe(function(value) {
            this._nextFourAppointments = value
        })
    }
}

And the attempt to display in the view as so:

  <li *ngFor="#appointment of _nextFourAppointments.availabilities | async">
         <div class="text-left appointment-flex">{{appointment | date: 'EEE' | uppercase}}

However, availabilities isn't yet a property of the observable object so it errors out, even though I define it in the availabilities interface as so:

export interface Availabilities {
  "availabilities": string[],
  "length": number
}

How can I display an array asynchronously from an observable object with the async pipe and *ngFor? The error message I get is:

browser_adapter.js:77 ORIGINAL EXCEPTION: TypeError: Cannot read property 'availabilties' of undefined
10
  • with the latest angular-rc1 the syntax is *ngFor="let appointment of _nextFourAppointments.availabilities | async"> Commented Jun 7, 2016 at 3:39
  • this is true, but it is not causing the error. it simply throws a warning. Commented Jun 7, 2016 at 3:40
  • 3
    I believe there is a typo somewhere. The error says availabilties while there should be availabilities Commented Jun 7, 2016 at 3:46
  • fixed spelling error, but availabilities isn't the problem, the error is that the observable is undefined at the time the template is parsed, and despite the async pipe it is still trying to find the key I believe Commented Jun 7, 2016 at 5:12
  • In AppointmentChoiceStore I think you can just return the Subject instead of putting it into a new Observable. BehaviorSubject extends from Observable. In AppointmentChoiceComponent you subscribe to the AppointmentChoiceStore observable. That should return you a Availabilities object (and not a string[] observable) that you put into _nextFourAppointments. So change _nextFourAppointments: Observable<string[]> to _nextFourAppointments: Availabilities and update the template accordingly. @Ivan: that doesn't explain why the error is about reading property 'X' from undefined. Commented Jun 7, 2016 at 5:14

4 Answers 4

178

Here's an example

// in the service
getVehicles(){
    return Observable.interval(2200).map(i=> [{name: 'car 1'},{name: 'car 2'}])
}

// in the controller
vehicles: Observable<Array<any>>
ngOnInit() {
    this.vehicles = this._vehicleService.getVehicles();
}

// in template
<div *ngFor='let vehicle of vehicles | async'>
    {{vehicle.name}}
</div>
Sign up to request clarification or add additional context in comments.

4 Comments

my get function is returning a subject though: public _appointmentChoices: Subject<any> = new Subject() getAppointments() { return this._appointmentChoices.map(object=>object.availabilities).subscribe() } , in the controller when I set it equal, I get the error: browser_adapter.js:77Error: Invalid argument '[object Object]' for pipe 'AsyncPipe', how do I turn the subject into an observable?
public _appointmentChoices: Subject<any> = new Subject() getAppointments() { return (this._appointmentChoices.map(object=>object.availabilities).asObservable()) } } this gives me the error: property asObservable does not exist on type observable, but _appointmentChoices is a Subject?
It already was an observable! I just needed to subscribe to it!
I had an additional problem with integrating subjects. Here's a StackBlitz using observables and subjects: stackblitz.com/edit/subject-as-observable-list-example
27

Who ever also stumbles over this post.

I belive is the correct way:

  <div *ngFor="let appointment of (_nextFourAppointments | async).availabilities;"> 
    <div>{{ appointment }}</div>
  </div>

Comments

6

I think what u r looking for is this

<article *ngFor="let news of (news$ | async)?.articles">
<h4 class="head">{{news.title}}</h4>
<div class="desc"> {{news.description}}</div>
<footer>
    {{news.author}}
</footer>

Comments

2

If you don't have an array but you are trying to use your observable like an array even though it's a stream of objects, this won't work natively. I show how to fix this below assuming you only care about adding objects to the observable, not deleting them.

If you are trying to use an observable whose source is of type BehaviorSubject, change it to ReplaySubject then in your component subscribe to it like this:

Component

this.messages$ = this.chatService.messages$.pipe(scan((acc, val) => [...acc, val], []));

Html

<div class="message-list" *ngFor="let item of messages$ | async">

2 Comments

Instead of the scan operator you can use .pipe(toArray())
Another pitfall when creating your own Subject is not calling complete(). The accumulator will never run.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.