1

I am just beginning with angular, and after trying to understand myself what is going on, I just couldn't come up with a solution. I have a simple template:

<div *ngIf="allProducts$ | async as products">
  <div *ngIf="products.size > 0; else noProducts">
    <ul>
      <li *ngFor="let product of products" >
        <span>{{product.id}}</span>
        <span>{{product.name}}</span>
        <span (click)="productDetail(product)">Go to detail</span>
      </li>
    </ul>
  </div>
  <ng-template #noProducts>There are no products yet.</ng-template>
</div>

With a class:

import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs';
import {Product} from '../../model/product';
import {Router} from '@angular/router';
import {ProductService} from '../../service/product.service';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {

  public allProducts$: Observable<Product[]>;

  constructor(private router: Router,
              private productService: ProductService) {}

  ngOnInit() {
    console.log('constructing ProductListComponent');
    this.allProducts$ = this.productService.getAllProducts();
  }

  productDetail(product: Product) {
    this.router.navigate([`product/${product.id}`]);
  }

}

And lastly, the service:

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {API_URL} from '../../app.constants';
import {Product} from '../model/product';
import {first, map} from 'rxjs/operators';
import {BehaviorSubject, Observable} from 'rxjs';

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

  constructor(private http: HttpClient) {
  }

  addProduct(product: Product) {
    return this.http.post<Product>(`${API_URL}/product`, product, {observe: 'response'});
  }

  getAllProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(`${API_URL}/product`, {observe: 'response'})
      .pipe(map(response => response.body), first());
  }
}

Pretty simple stuff. I just can't get it to render. So I have tried creating a behaviour subject in the service and subscribing it to the http observable, and returning it to component instead of the original http observable, thinking that maybe component did not have a chance to subscribe in time, as is mentioned in this SO. No luck.

Next, inspired by this SO question, I tried calling markForCheck manually in the subscription, removing the async pipe altogether. Again, nothing.

I was not able to find any other solution, but these two are the most recurring approaches, and everywhere it just seems to work, like here or here. Why doesn't it work for me? Clearly, there is something simple eluding me. When I log the response body in the console in the subscription lambda, I can see the product entities I expect from backend, but neither async pipe or subscription in ngOnInit work. I have also seen an approach using ngZone.run, but I get the feeling that would simply accomplish the same thing as regular subscription and markForCheck.

Jozef Morvay
  • 133
  • 3
  • 16
  • 1
    By products.size you mean products.length ? – Milos Kovacevic Mar 11 '19 at 22:09
  • 1
    Can you verify that your HTTP call is returning the data ? Sometimes I like to debug by doing something like:
     {{allProducts$ | json | async }}
    " Just to make sure I'm getting data to the template. Also, if you don't need the HTTP header info, the HttpClient now returns the JSON response body as an observable by default.
    – DerrickF Mar 11 '19 at 22:15
  • @MilosKovacevic I do, indeed. – Jozef Morvay Mar 12 '19 at 10:13
  • @DerrickF the data was being received correctly, but thanks for the tip about not having to include response option in HTTP request. – Jozef Morvay Mar 12 '19 at 10:18

1 Answers1

3

This doesn't look right...

<div *ngIf="products.size > 0; else noProducts">

Do you mean:

<div *ngIf="products.length > 0; else noProducts">
Will Taylor
  • 1,650
  • 9
  • 23