1

I'm getting started with Observable in Angular 2 and I can't figure out how to use them properly in my views.

I'm using Angular 2 with angular-redux, and using the @select() decorator to retrieve my selectedMovie$ from the redux store. This part works fine, the component basically dispatch a redux event to set the default selectedMovie$ upon init. The redux store is correctly updated, but when I try to consumme it in the view, I face some issues.

import { Component, OnInit } from '@angular/core';
import { NgRedux, select } from '@angular-redux/store';
import { MovieActions} from '../store/app.actions';
import { IAppState} from '../store/reducers';
import { MovieService } from '../movie.service';
import { Observable } from 'rxjs/Observable';
import { IMovie } from '../movie.model';

@Component({
  selector: 'app-movies-container',
  templateUrl: './movies-container.component.html',
  styleUrls: ['./movies-container.component.css'],
  providers: [MovieService]
})
export class MoviesContainerComponent implements OnInit {
  movies: Array<IMovie>;
  @select() readonly selectedMovie$: Observable<IMovie>; // HERE - Get data from the redux store

  constructor(
    private movieService: MovieService,
    private movieActions: MovieActions,
    private ngRedux: NgRedux<IAppState>
  ) { }

  ngOnInit() {
    // Fetch movies and use the first one as default displayed movie.
    this.getMovies() // HERE - Takes the first movie and make it the default selectedMovie by dispatching a redux action
      .then(movies =>
        this.ngRedux.dispatch(this.movieActions.changeSelectedMovie(this.movies[0]))
      );
  }

  getMovies() { // HERE: Call an API, returns an array of movies in data.results
    return this.movieService.getMostPopular()
      .then(data => this.movies = data.results);
  }

  onSelect(movie: IMovie) {
    this.ngRedux.dispatch(this.movieActions.changeSelectedMovie(movie));
  }
}

Here comes the view:

<div *ngIf="movies">
  <md-list>
    <h3 md-subheader>Most popular movies NOW!</h3>
    <pre>
      {{(selectedMovie$ | async | json).id}} // This fails and displays nothing. I'd expect it to display the movie id
    </pre>
    <md-list-item
      *ngFor="let movie of movies"
      [class.selected]="movie.id === (selectedMovie$ | async | json).id" // This is a real deal, I don't know what's the syntax to use. I wanted to compare ids
      (click)="onSelect(movie)"
    >
      <img src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" />
      {{movie.title}}
    </md-list-item>
  </md-list>

  <app-movie-card [movie]="selectedMovie$ | async | json"></app-movie-card> // This component gets the object correctly formatted
</div>

Maybe I'm just not using the right syntax. Or maybe I shouldn't use an Observer in the view in the first place?


Edit: Solution

<div *ngIf="movies && (selectedMovie$ | async); let selectedMovie">
  <md-list>
    <h3 md-subheader>Most popular movies NOW!</h3>
    <md-list-item
      *ngFor="let movie of movies"
      [class.selected]="movie.id === selectedMovie.id"
      (click)="onSelect(movie)"
    >
      <img src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" />
      {{movie.title}}
    </md-list-item>
  </md-list>

  <app-movie-card [movie]="selectedMovie"></app-movie-card>
</div>
Vadorequest
  • 16,593
  • 24
  • 118
  • 215

1 Answers1

4

The problem results from a common misconception that JSON is a synonym for plain object. It isn't.

json pipe converts input into actual JSON string. So (selectedMovie$ | async | json) expression evaluates to a string and doesn't have id property.

It is helpful to use AoT compilation, because it allows to detect type problems in template and would likely result in type error in this case.

It should be (selectedMovie$ | async).id instead.

If (selectedMovie$ | async) is used more than once (like in this case), it will result in several subscriptions. It can be optimized by assigning it to local variable, as explained here:

<div *ngIf="movies">
  <ng-container *ngIf="(selectedMovie$ | async); let selectedMovie">
    ...
    {{selectedMovie.id}}
    ...
  </ng-container>
</div>
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Very smart trick. Really useful to store the value for later use within the same template. Any particular interest using the `` instead of a div? – Vadorequest Sep 27 '17 at 19:20
  • 1
    See https://stackoverflow.com/questions/39547858/angular-2-ng-container . Tl;DR: it doesn't pollute layout. – Estus Flask Sep 27 '17 at 19:26