简体   繁体   中英

Angular ngFor trackBy not working. Not Updating DOM

Hi y'all my dom is not being updated with trackBy for some reason. I have another component where trackBy is working great but for some reason I can't get it to work on my new component. I have to refresh the page everytime something is added to groceryList and I don't know why?

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>

Component Code:

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

And if your wondering, the console.log("tracking") inside of trackByIdGroceryCode() is being called when adding to my groceryList array. So I'm not sure why my dom isnt being updated unless I refresh the page

Here's my console output if you're curious

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">&times;</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">&times;</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">&times;</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: I Learned that things update fine when deleting objects but when I call my function to add to my grocery component from my recipe component things don't update. I think my issue is that things arn't being called in the order that I think they are. I still don't know how to fix this but I really appreciate everyone that is trying to help

//

Looks like this could be an angular lifecycle issue; maybe the template is not getting updated after the subscriptions that are called when you run refreshGroceryList() . Try doing a manual change detection after you update the data, like so:

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

}

The problem is you are trying to bind to aria and data attributes. If you change all the aria- attributes to attr.aria- and data- attributes to attr.data- it will work.

Here is a working StackBlitz https://stackblitz.com/edit/angular-ivy-kp6aev?file=src%2Fapp%2Fapp.component.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM