javascript - Angular ngFor trackBy 不工作。不更新 DOM
问题描述
大家好,由于某种原因,我的 dom 没有使用 trackBy 更新。我有另一个组件,其中 trackBy 运行良好,但由于某种原因,我无法让它在我的新组件上运行。每次将某些内容添加到杂货清单时,我都必须刷新页面,但我不知道为什么?
HTML:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<div class="accordion" id="accordionExample">
<div class="card" *ngFor="let grocery of groceryList;trackBy:trackByIdGroceryCode;index as index;">
<div class="card-header" id="grocery1{{index}}">
<h5 class="mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" attr.data-target="#grocery2{{index}}" aria-expanded="false" aria-controls="grocery2{{index}}">
{{grocery.recipeName}}
</button>
</h5>
</div>
<div id="grocery2{{index}}" class="collapse" aria-labelledby="grocery1{{index}}" data-parent="#accordionExample">
<div class="card-body">
<ul class="list-group" id="filterList">
<li class="list-group-item">
<a href="#" class="list-down-btn" data-toggle="#subgroup"><span class="glyphicon glyphicon-chevron-down"></span></a>
<ul id="subgroup" class="list-group">
<li class="list-group-item" *ngFor="let ingredient of grocery.ingredients">{{ingredient}}</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
<mat-icon svgIcon="shopping_cart"></mat-icon>
组件代码:
import { Component, OnInit, NgModule } from '@angular/core';
import {GetRecipesService} from '../getrecipes.service'
import { MatIconRegistry } from "@angular/material/icon";
import { DomSanitizer } from "@angular/platform-browser";
@Component({
selector: 'app-grocery-sidebar',
templateUrl: './grocery-sidebar.component.html',
styleUrls: ['./grocery-sidebar.component.css']
})
export class GrocerySidebarComponent implements OnInit {
constructor(getRecipesService: GetRecipesService,private matIconRegistry: MatIconRegistry,private domSanitizer: DomSanitizer) {
getRecipesService.getGroceryList().subscribe(promise=>{
this.groceryList = promise;
this.groceryList = this.groceryList.data;
});
this.recipeService=getRecipesService;
this.matIconRegistry.addSvgIcon("shopping_cart",this.domSanitizer.bypassSecurityTrustResourceUrl("../assets/shopping-cart-solid.svg"));
}
addToGroceryList(recipeName,recipeIngredients){
this.recipeService.addToGroceryList(recipeName,recipeIngredients).subscribe(promise=>{
console.log("addToGroeryList Promise: "+promise);
this.refreshGroceryList();
});
}
refreshGroceryList(){
this.recipeService.getGroceryList().subscribe(promise=>{
console.log("refreshed groceryList: "+promise.data)
this.groceryList = promise.data;
console.log(this.groceryList);
})
}
deleteGroceryRecipeById(recipeId){
this.recipeService.deleteGroceryRecipeById(recipeId).subscribe(promise=>{
this.refreshGroceryList();
});
}
public trackByIdGroceryCode(index: number, grocery: any): string {
console.log("tracking");
return grocery._id;
}
ngOnInit(): void {
}
recipeService;
groceryList;
showFiller=false;
}
如果您想知道,添加到我的groceryList 数组时会调用trackByIdGroceryCode() 内部的console.log("tracking")。所以我不确定为什么我的 dom 没有更新,除非我刷新页面
如果你好奇,这是我的控制台输出
tracking grocery-sidebar.component.ts:44:12
addToGroeryList Promise: [object Object] grocery-sidebar.component.ts:23:14
tracking grocery-sidebar.component.ts:44:12
refreshed groceryList: [object Object],...,[object Object] grocery-sidebar.component.ts:31:14
Array(23) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ] grocery-sidebar.component.ts:33:14
tracking
{{ groceryList | json }}
<div class="accordion" id="accordionExample">
<div class="card" *ngFor="let grocery of groceryList; index as index;">
<div class="card-header" [id]="'grocery1'+index">
<h5 class="mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" [attr.data-target]="'#grocery2'+index" aria-expanded="false" [aria-controls]="'grocery2'+index">
{{grocery.recipeName}}
</button>
</h5>
</div>
<div [id]="'grocery2' + index" class="collapse" [aria-labelledby]="'grocery1'+index" data-parent="#accordionExample">
<div class="card-body">
<ul class="list-group" id="filterList">
<li class="list-group-item">
<a href="#" class="list-down-btn" data-toggle="#subgroup"><span class="glyphicon glyphicon-chevron-down"></span></a>
<ul id="subgroup" class="list-group">
<li class="list-group-item" *ngFor="let ingredient of grocery.ingredients">{{ingredient}}</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
<mat-icon svgIcon="shopping_cart"></mat-icon>
Recipe Component that invokes groceryComponent:
import {Component} from '@angular/core';
import {GetRecipesService} from './getrecipes.service'
import { TagInputModule } from 'ngx-chips';
import {GrocerySidebarComponent} from "./grocery-sidebar/grocery-sidebar.component";
TagInputModule.withDefaults({
tagInput: {
placeholder: 'Add a ag',
// add here other default values for tag-input
},
dropdown: {
displayBy: 'my-display-value',
// add here other default values for tag-input-dropdown
}
});
@Component({
selector: 'recipes', //<recipes>
styleUrls: ['./recipes.component.css'],
template: `
<script src="angular.min.js"></script>
<script src="ng-tags-input.min.js"></script>
<div class="recipeContainer container-fluid">
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="recipeNameInput1">Recipe Name</label>
<input [(ngModel)] ="formRecipeName" name="formRecipeName" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
<tag-input [(ngModel)]="formIngredients" id="ingredientTags" [modelAsStrings]="true" name="formIngredients" [secondaryPlaceholder]="'Enter Ingredient'"> </tag-input>
</div>
<button type="submit" class="btn btn-primary" (click)="addRecipe()" data-dismiss="modal">Submit</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Are you Sure Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<button type="submit" class="btn btn-primary" (click)="deleteRecipeInBuffer()" data-dismiss="modal">Delete</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="album py-5 bg-light">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit" data-toggle="modal" data-target="#exampleModal">Add Recipe</button>
</li>
<li class="nav-item">
</li>
</ul>
<form class="form-inline mt-2 mt-md-0">
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
<div class="row">
<div class="col-md-4" *ngFor = "let recipe of recipeList;trackBy:trackByIdCode">
<div class="card mb-4 box-shadow">
<sup>
<button type="button" data-toggle="modal" data-target="#deleteModal" class="close" aria-label="Close" (click)="prepareToDelete(recipe._id)">
<span aria-hidden="true">×</span>
</button>
</sup>
<h5 class="card-title">{{recipe.recipeName}} </h5>
<div class="card-body" >
<p class="card-text">{{recipe.recipeIngredients}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="addToGroceryList(recipe.recipeName,recipe.recipeIngredients)">Add To Grocery List</button>
</div>
<small class="text-muted">9 mins</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
TODO: Edit Recipe. Ingreidents with quantity. Ingredients with style (Chopped. Diced. Sautee..etc). Search or Filter (by name or ingredient).
TODO: Add to grocery List. Undo Button
`,
})
export class RecipesComponent{
constructor(getRecipesService: GetRecipesService,groceryList:GrocerySidebarComponent){
getRecipesService.getRecipes().subscribe(promise=>{
this.recipeList = promise;
this.recipeList = this.recipeList.data;
console.log(this.recipeList);
});
this.recipeService=getRecipesService;
this.groceryList = groceryList;
}
addToGroceryList(recipe,ingredients){
this.groceryList.addToGroceryList(recipe,ingredients);
}
//when user presses x on card, the id is stored here. Then are you sure window appears
//if yes on are you sure then delete whats in buffer
//else clear what's in buffer
prepareToDelete(recipeId){
this.deleteBuffer = recipeId;
}
//if yes after are you sure, delete whats in buffer
deleteRecipeInBuffer(){
this.deleteRecipe(this.deleteBuffer);
}
addRecipe(){
this.recipeService.addRecipe(this.formRecipeName,this.formIngredients).subscribe(promise=>{
console.log("promise"+promise);
this.refreshRecipeList();
this.formIngredients = undefined;
this.formRecipeName = undefined;
});
}
deleteRecipe(recipeId){
this.recipeService.deleteRecipe(recipeId).subscribe(promise=>{
console.log(promise);
this.refreshRecipeList();
})
}
refreshRecipeList(){
this.recipeService.getRecipes().subscribe(promise=>{
console.log("refreshed");
this.recipeList = promise.data;
});
}
public trackByIdCode(index: number, recipe: any): string {
return recipe._id;
}
deleteBuffer;//buffer is used to store recipeId => are you sure window comes up. if yes then delete whats in deleteBuffer
formRecipeName;//form value in modal
formIngredients; //form value in modal
recipeService;//http access service
recipeList;//list of all recipes recieved from recipeService
groceryList;
}
UPADTE:我了解到,在删除对象时,事情更新得很好,但是当我调用我的函数从我的食谱组件添加到我的杂货组件时,事情并没有更新。我认为我的问题是事情没有按照我认为的顺序被调用。我仍然不知道如何解决这个问题,但我非常感谢所有试图提供帮助的人
//
解决方案
看起来这可能是一个角度生命周期问题;也许在您运行时调用的订阅之后模板没有更新refreshGroceryList()
。更新数据后尝试进行手动更改检测,如下所示:
constructor(private cdr: ChangeDetectorRef, ...) {}
refreshGroceryList(){
this.recipeService.getGroceryList().subscribe(promise=>{
console.log("refreshed groceryList: "+promise.data)
this.groceryList = promise.data;
this.cdr.detectChanges();
console.log(this.groceryList);
})
}
推荐阅读
- linux - 如何在 shell 脚本中访问“可变位置”命令行参数?
- reactjs - 是否可以从同一个自定义 Word 选项卡中调用 VSTO 插件和 Office.js 插件?
- php - 用于编辑数据库的表单操作
- linux - 为什么 os.OpenFile 不会创建 777 文件
- android - 如何在本机反应中创建自定义通知布局?
- javascript - 如何将用户外键设置为当前用户 django ajax
- python - kivymd MDbottomnavigation on_tab_press
- excel - VBA 去掉逗号
- javascript - Three.js:如何翻转轴使Z轴变为X轴
- java - 如何使用其他分区键查询 dynamoDB