Javascript 在单独的组件中调用方法时角度Dom不更新

Javascript 在单独的组件中调用方法时角度Dom不更新,javascript,angular,Javascript,Angular,大家好,我有一个组件(我们称之为配方组件),它有一个对另一个组件的引用(我们称之为杂货组件) “我的食谱”组件引用了“杂货店”组件。my recipe组件中的一个方法使用杂货店组件ref调用杂货店组件中的一个方法 配方组件方法称为addToGroceryList(recipeName,配料)。此方法使用杂货组件引用调用GroceryComponent.addToGroceryList(recipeName,配料) 当我这样做的时候,我的dom不会得到更新,我也不知道为什么。我曾经在我的杂货店组件

大家好,我有一个组件(我们称之为配方组件),它有一个对另一个组件的引用(我们称之为杂货组件)

“我的食谱”组件引用了“杂货店”组件。my recipe组件中的一个方法使用杂货店组件ref调用杂货店组件中的一个方法

配方组件方法称为addToGroceryList(recipeName,配料)。此方法使用杂货组件引用调用GroceryComponent.addToGroceryList(recipeName,配料)

当我这样做的时候,我的dom不会得到更新,我也不知道为什么。我曾经在我的杂货店组件html中创建一个临时按钮来调用groceryComponent.addToGroceryList(“test1”,“test”]),当我使用这个模拟按钮时,dom会很好地更新。所以我知道问题是因为我通过引用调用方法

无论如何,这是我的密码。groceryList是我对其他组件的引用

接收组件:

    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;
}
import { Component, OnInit, NgModule,ChangeDetectorRef, ChangeDetectionStrategy } 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'],
  changeDetection: ChangeDetectionStrategy.Default,
  })

export class GrocerySidebarComponent {

  constructor(getRecipesService: GetRecipesService,private matIconRegistry: MatIconRegistry,private domSanitizer: DomSanitizer,private cdr:ChangeDetectorRef) { 
    getRecipesService.getGroceryList().subscribe(promise=>{
      this.groceryList = promise.data;
  });
    this.recipeService=getRecipesService;
    this.matIconRegistry.addSvgIcon("shopping_cart",this.domSanitizer.bypassSecurityTrustResourceUrl("../assets/shopping-cart-solid.svg"));
    this.CDR = cdr;
  }

  addToGroceryList(recipeName,recipeIngredients){
    console.log("Entered addToGroceryList");
    this.recipeService.addToGroceryList(recipeName,recipeIngredients).subscribe(promise=>{
      console.log("promise returned from addToGroceryList()")
      this.refreshGroceryList();
    });
    
  }

  refreshGroceryList(){
    this.recipeService.getGroceryList().subscribe(promise=>{
      this.groceryList = promise.data;
      console.log("refreshed");
    })
  }

  deleteGroceryRecipeById(groceryId){
    console.log("Delete requested: "+groceryId);
    this.recipeService.deleteGroceryRecipeById(groceryId).subscribe(promise=>{
      this.refreshGroceryList();
    });
  }


  public trackByCode(index: number, recipe: any): string {
    console.log("tracking");
    return recipe._id;
}
  CDR;
  recipeService;
  groceryList: object[];
  showFiller=false;
}
杂货店HTML:

<div class="accordion" id="accordionExample">
  <div class="card" *ngFor="let grocery of groceryList;trackBy:trackByCode; index as index;">
    <div class="card-header" [id]="'grocery1'+index">
      <h5 class="mb-0">
        <button class="btn btn-link" type="button" attr.data-toggle="collapse" [attr.data-target]="'#grocery2'+index" attr.aria-expanded="false" [attr.aria-controls]="'grocery2'+index">
          {{grocery.recipeName}}
        </button>
        <sup>
          <button type="button" class="close" aria-label="Close" (click)="deleteGroceryRecipeById(grocery._id)">
          <span aria-hidden="true">&times;</span>
          </button>
      </sup>
      </h5>
    </div>

    <div [id]="'grocery2' + index" class="collapse" [attr.aria-labelledby]="'grocery1'+index" attr.data-parent="#accordionExample">
      <div class="card-body">
        <ul class="list-group" id="filterList">
          <li class="list-group-item">
            <a href="#" class="list-down-btn" attr.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>

<sup>
  <button type="button" class="close" aria-label="Close" (click)="addToGroceryList('test',['test']) ">
  <span aria-hidden="true">&times;</span>
  </button>
</sup>

<mat-icon svgIcon="shopping_cart"></mat-icon>

{{screery.recipeName}
&时代;
    • {{{Component}
&时代;

当我从杂货店组件中调用addToGroceryList()时,再次重复dom更新也很好。但是当我使用ref从另一个组件调用addToGroceryList时,dom不会更新。有人知道这里发生了什么吗?

我假设
RecipesComponent
是这个答案中
GrocerySidebarComponent
的后代,否则组件依赖注入在这里不起作用。但我也认为这是一个直接的孩子

这不起作用的原因是,注入祖先组件虽然可能,但在angular中并不推荐,也不受angular的更改检测的支持
GrocerySdebarComponent
没有意识到它的子级已经注入了它(它不应该这样),因此不知道什么时候调用它的函数,它需要运行更改检测

您应该使用来自子级或共享服务模型的
输出
。我将在这里讨论
Output
方法,当孩子是直接孩子时,该方法最有效。除此之外,共享服务会更好

在配方组件中,删除父注入,并进行以下更新:

@Output()
onAddToGroceryList = new EventEmitter<{recipe, ingredients}>()

addToGroceryList(recipe,ingredients){
    this.onAddToGroceryList.emit({recipe,ingredients});
}
这将从子组件正确触发父组件中的更改检测。它还使
RecipesComponent
更具可移植性,因为它不依赖于直接注入特定的父级,它只是发出一个父级可能感兴趣也可能不感兴趣的一般事件

编辑:

根据评论,您需要一个共享服务模型。。。将此服务添加到项目中:

@Injectable({providedIn: 'root'})
export class GroceryListService {
  private addToGroceryListSource = new Subject<{recipe, ingredients}>();
  addToGroceryList$ = this.addToGroceryListSource.asObservable();
  addToGroceryList(recipe, ingredients) {
    this.addToGroceryListSource.next({recipe, ingredients});
  }
}
然后在
GrocerySidebarComponent
中,注入服务并订阅事件:

constructor(private getRecipesService: GetRecipesService, private groceryListService: GroceryListService, private matIconRegistry: MatIconRegistry,private domSanitizer: DomSanitizer,private cdr:ChangeDetectorRef) { 
  this.groceryListService.addToGroceryList$.subscribe(
    ({recipe, ingredients}) => this.addToGroceryList(recipe, ingredients)
  )

不,您将添加
(onAddToGroceryList)=“addToGroceryList($event.recipe,$event.components)”
recipes组件
元素选择器位于
GrocerySidebarComponent
模板中的任何位置。我不知道实际的选择器是什么,或者你的实际模板是什么样子,因为你没有包括它们。嗯,这没有多大意义。您的
RecipesComponent
应该是
GrocerySidebarComponent
的子组件,以便组件注入正常工作。啊。我懂了。是的,那是毫无意义的,完全不是预期用途。您甚至没有与页面上相同的
GrocerySidebarComponent
实例<代码>输出
在此处不起作用。您需要共享服务。您需要在serviceadded共享服务方法中使用
rxjs
和主题
// shorthand declare private to add to `this`
constructor(private getRecipesService: GetRecipesService, private groceryList:GroceryListService){
    getRecipesService.getRecipes().subscribe(promise=>{
        this.recipeList = promise;
        this.recipeList = this.recipeList.data;
        console.log(this.recipeList);
    });
    
}
addToGroceryList(recipe,ingredients){
    this.groceryList.addToGroceryList(recipe,ingredients);
}
constructor(private getRecipesService: GetRecipesService, private groceryListService: GroceryListService, private matIconRegistry: MatIconRegistry,private domSanitizer: DomSanitizer,private cdr:ChangeDetectorRef) { 
  this.groceryListService.addToGroceryList$.subscribe(
    ({recipe, ingredients}) => this.addToGroceryList(recipe, ingredients)
  )