'Angular not passing object to html page

I am having issues with learning angular, I didn't use to have these problems before, but now when I do these demos things work great until I switch from using an object directly defined in the *.component.ts file to pulling the object from somewhere else. In 2 different instances use 2 different mechanisms (microservice, service component serving an object directly). This particular one is the best example. The code below works fine, but the code below that doesn't pass the card object to the page. Debug is showing that the object is being filled, but when it gets to the HTML page it is undefined.

        
import { Component } from '@angular/core';
import { map } from 'rxjs/operators';
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { AppService } from '../app.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent {
  /** Based on the screen size, switch from standard to one column per row */
  //cards = [];
  cardsForHandset = [];
  cardsForWeb = [];

  //isHandset: boolean = false;
  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
    map(({ matches }) => {
      if (matches) {
        return [
          { title: 'Card 1', cols: 2, rows: 1 },
          { title: 'Card 2', cols: 2, rows: 1 },
          { title: 'Card 3', cols: 2, rows: 1 },
          { title: 'Card 4', cols: 2, rows: 1 }
        ];
      }
      return [
        { title: 'Card 1', cols: 2, rows: 1 },
        { title: 'Card 2', cols: 1, rows: 1 },
        { title: 'Card 3', cols: 1, rows: 2 },
        { title: 'Card 4', cols: 1, rows: 1 }
      ];
    })
  );

  constructor(private breakpointObserver: BreakpointObserver,
    public appService: AppService,
    ) { }

}

HTML

<div class="grid-container">
  <h1 class="mat-h1">Todays Deals</h1>
  <mat-grid-list cols="2" rowHeight="350px">
    <mat-grid-tile *ngFor="let card of cards | async" [colspan]="card.cols" [rowspan]="card.rows">
      <mat-card class="dashboard-card">
        <mat-card-header>
          <mat-card-title>
            {{card.title}}
          </mat-card-title>
        </mat-card-header>
      </mat-card>
    </mat-grid-tile>
  </mat-grid-list>
</div>

This is the code that doesn't work.

        import { Component } from '@angular/core';
import { map } from 'rxjs/operators';
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { AppService } from '../app.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent {
  /** Based on the screen size, switch from standard to one column per row */
  cards = [];
  cardsForHandset = [];
  cardsForWeb = [];

  isHandset: boolean = false;
  isHandsetObserver: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
    map(({ matches }) => {
      if (matches) {
        return true;
      }
      return false;
    })
  );

  constructor(private breakpointObserver: BreakpointObserver,
    public appService: AppService,
    ) { }

  ngOnInit() {
    this.isHandsetObserver.subscribe(currentObserverValue => {
      this.isHandset = currentObserverValue;
      this.loadCards();
      this.cards.push();
    });

    this.appService.getDeals().subscribe(
      response => {
        this.cardsForHandset = response.handsetCards;
        this.cardsForWeb = response.webCards;
        this.loadCards();
      },
      error => {
        // alert('There was an error in receiving data from server. Please come again later!');
       }
    );
  }

  loadCards() {
    this.cards = this.isHandset ? this.cardsForHandset : this.cardsForWeb;
  }

}

HTML - Is the same above, but with the async removed.

Service

        import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AppService {

  constructor(public httpClient:HttpClient) { }

  getDeals(): Observable<any> {
    return this.httpClient.get('http://localhost:3000/deals');
  }
}

Server Side Microservice

        var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  let jsaonResponse = {
    "handsetCards": [
      { title: 'Card 1', cols: 2, rows: 1 },
      { title: 'Card 2', cols: 2, rows: 1 },
      { title: 'Card 3', cols: 2, rows: 1 },
      { title: 'Card 4', cols: 2, rows: 1 }
    ],
    "webCards": [
      { title: 'Card 1', cols: 2, rows: 1 },
      { title: 'Card 2', cols: 1, rows: 1 },
      { title: 'Card 3', cols: 1, rows: 2 },
      { title: 'Card 4', cols: 1, rows: 1 }
    ]
  }

  res.json(jsaonResponse);
});

module.exports = router;

Here are the errors:

Error Message



Solution 1:[1]

This is a typescript typing error. Due to cards being an array and your getDeals returning a single object typescript doesn't know what that type is. A good solution would be to create a model:

card.model.ts:

export interface Cart {
  title: string;
  cols: number;
  rows: number;
}

getDeals in app.service.ts would return an array of Card (don't forget to import your model):

getDeals(): Observable<Card[]> {
  return this.httpClient.get('http://localhost:3000/deals');
}

And your variables in home.component.ts would look like this:

cards: Card[] = [];
cardsForHandset: Card[] = [];
cardsForWeb: Card[] = [];

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Halil Bahar