Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async pipe does not fill object data into template

Can anyone help me see if there is a syntax error here in my template? It does not give error, but does not fill in data into the template either:

<div *ngIf="(hero | async)">
  <h2>{{hero}}</h2>
  <h2>{{hero.name}} details!</h2>
  <div>
    <label>_id: </label>{{hero._id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="name" />
  </div>
  <button (click)="goBack()">Back</button>
</div>

Component code

export class HeroDetailComponent implements OnInit {
    errorMessage: string;
       
    @Input() hero: Observable<Hero>;
        
    constructor(
        private _heroService: HeroService,
        private _routeParams: RouteParams
    ) {}
       
    ngOnInit() {
        let _id = +this._routeParams.get('_id');
        this._heroService.loadHero(_id);
        this.hero = this._heroService.hero$;
        this.hero.subscribe(data => 
           console.log(data)
        );
    }
}

The console.log(data) prints:

Object {_id: 11, name: "Mr. Nice"}

which means that the data is correctly retrieved.

The <div> block also shows up, which mean *ngIf sees the object as non-empty.

<h2>{{hero}}</h2> shows [object Object].

But why the {{hero.name}} is not showing?

like image 253
Shawn Avatar asked Apr 22 '16 21:04

Shawn


3 Answers

Objects are a bit tricky with the async pipe. With an Observable that contains an array, we can use NgFor and create a local template variable (hero below) that gets assigned each item of the array after the async pipe extracts the array from the Observable. We can then use that variable elsewhere in the template:

<div *ngFor="let hero of heroes | async">   {{hero.name}} </div> <!-- we can't use hero here, outside the NgFor div --> 

But with an Observable that contains a single object, I'm not aware of any way to create a local template variable that will reference that object. Instead, we need to do something more complicated:

<div>{{(hero | async)?.name}}</div> 

And we would need to repeat that for each property of the object we want to display. (The above line assumes that component property hero is an Observable.)

It is probably easier to assign the object (that is inside the Observable, hero$ below) to a property of the component, using component logic:

this._heroService.hero$.subscribe(data => this.hero = data.json()); 

and then use NgIf or the Elvis/safe navigation operator to display the data in the view:

<div *ngIf="hero">{{hero.name}}</div> <!-- or --> <div>{{hero?.name}}</div> 
like image 92
Mark Rajcok Avatar answered Sep 27 '22 21:09

Mark Rajcok


This is now possible using the 'as' syntax, available in v4.0.0:

<span *ngIf="user$ | async as user; else loadingUserInfo">
 {{user.firstName}} {{user.lastName}}
</span>
<ng-template #loadingUserInfo>
  Loading user information...
</ng-template>

More details available in the RFC thread on github.

like image 43
jmcmichael Avatar answered Sep 27 '22 22:09

jmcmichael


Another option would be to use @Input and leverage smart/dumb component approach. In your smart component you can pass the async object to the dumb component then in the dumb component you can use it like a normal object.

The idea is that your smart component deals with logic and data and the dumb component handles presentation.

Smart component:

<dumb-component [myHero]="hero$ | async"></dumb-component>

Dumb component class:

@Input() myHero: Hero;

Dumb component template:

<div>{{ myHero.name }}</div>
like image 36
chris cooley Avatar answered Sep 27 '22 22:09

chris cooley