Search code examples
angulartypescriptvariablesangular-ng-if

Not understanding use of template variables in *ngIf statement


my objective is to eventually make a setting where the user can change the number of rows while looking at the catalog (spiritwoodart.com). I didn't even get past the first stage of fiddling when I ran into a problem. Guessing its a fundamental misunderstanding. Having trouble finding help (i.e. my search sucks: "why cant i put a template variables in ngif statement"). Please let me know if more info is needed. Thanks for any and all insights, or even flames for my newbiness.

So, when I change

this:

            <div *ngIf="(i+1) % n === 0"
                class="w-100"></div>

to this:

or this:

            <div #n='3' *ngIf="(i+1) % n === 0"
                class="w-100"></div>

or even this:

            <div *ngIf="(i+1) % {{3}} === 0"
                class="w-100"></div>

I get an error that looks like this:

compiler.js:485 Uncaught Error: Template parse errors:
There is no directive with "exportAs" set to "3" ("
                        [cart]="cart"></product-card>
                </div>
                <div [ERROR ->]#n='3' *ngIf="(i+1) % n === 0"
                    class="w-100"></div>
            </ng-container>
"): ng:///AppModule/ProductsComponent.html@12:21

context-template:

<div class="row">
    <div class="col-3">
        <product-filter [category]="category"></product-filter>
    </div>
    <div class="col">
        <div class="row"
            *ngIf="cart$ | async as cart">
            <ng-container *ngFor="let p of filteredProducts; let i = index">
                <div class="col">
                    <product-card [product]="p"
                        [cart]="cart"></product-card>
                </div>
                <div *ngIf="(i+1) % 3 === 0"
                    class="w-100"></div>
            </ng-container>
        </div>
    </div>
</div>

context-component:

import { Cart } from './../models/cart';
import { CartService } from './../cart.service';
import { Product } from './../models/product';
import { ProductService } from './../product.service';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import 'rxjs/add/operator/switchMap';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
  products: Product[] = [];
  filteredProducts: Product[] = [];
  category: string; // keep this field in this class in order to initialize it... then delegate it out
  cart$: Observable<Cart>;

  constructor(
    private route: ActivatedRoute,
    private productService: ProductService,
    private cartService: CartService
  ) {}

  async ngOnInit() {
    this.cart$ = await this.cartService.getCart();
    this.populateProducts();
  }

  private populateProducts() {
    this.productService
      .getAll()
      .switchMap(products => {
        this.products = products;
        return this.route.queryParamMap;
      })
      .subscribe(params => {
        this.category = params.get('category'); // initializing category field
        this.applyFilter();
      });
  }

  private applyFilter() {
    // setting the filtered products array
    this.filteredProducts = this.category
      ? this.products.filter(p => p.category === this.category)
      : this.products;
  }
}

Solution

  • # is template reference variable. It defers to DOM element and cannot be used like that.

    ng-init directive exists in AngularJS and serves this purpose, but it doesn't exist in Angular, primarily because it is prone to be misused. It's possible to recreate it, as shown in this answer.

    A workaround is to use a structural directive. A typical workaround for truthy value is ngIf structural directive:

    <ng-container *ngIf="3; let n">
      <div *ngIf="(i+1) % n === 0">
        ...
    

    It won't work if the value is falsy, because nested component won't be compiled. More universal workaround that supports falsy values is custom ngVar structural directive, as described here.