Not understanding use of template variables in *ng

2019-08-22 04:15发布

问题:

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;
  }
}

回答1:

# 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.



回答2:

For fellow newbs like me here's what I did for now with JB's advice... Thanks JB. And I hope this helps someone else.

just a few changes...

template:

<div class="row">
        <input #myInput (change)="changeColumnNumber(myInput.value)" type="number">
</div>

component:

  n = 4;

Full context:

template:

    <div class="row">
        <input #myInput (change)="changeColumnNumber(myInput.value)" type="number">
</div>

<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) % n === 0"
                    class="w-100"></div>
            </ng-container>
        </div>
    </div>
</div>

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>;
  n = 4;


  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;
  }

  changeColumnNumber(value) {
    console.log(value);
    this.n = value;
    console.log(this.n);
  }
}