Javascript 在单独的组件中调用方法时角度Dom不更新
大家好,我有一个组件(我们称之为配方组件),它有一个对另一个组件的引用(我们称之为杂货组件) “我的食谱”组件引用了“杂货店”组件。my recipe组件中的一个方法使用杂货店组件ref调用杂货店组件中的一个方法 配方组件方法称为addToGroceryList(recipeName,配料)。此方法使用杂货组件引用调用GroceryComponent.addToGroceryList(recipeName,配料) 当我这样做的时候,我的dom不会得到更新,我也不知道为什么。我曾经在我的杂货店组件html中创建一个临时按钮来调用groceryComponent.addToGroceryList(“test1”,“test”]),当我使用这个模拟按钮时,dom会很好地更新。所以我知道问题是因为我通过引用调用方法 无论如何,这是我的密码。groceryList是我对其他组件的引用 接收组件:Javascript 在单独的组件中调用方法时角度Dom不更新,javascript,angular,Javascript,Angular,大家好,我有一个组件(我们称之为配方组件),它有一个对另一个组件的引用(我们称之为杂货组件) “我的食谱”组件引用了“杂货店”组件。my recipe组件中的一个方法使用杂货店组件ref调用杂货店组件中的一个方法 配方组件方法称为addToGroceryList(recipeName,配料)。此方法使用杂货组件引用调用GroceryComponent.addToGroceryList(recipeName,配料) 当我这样做的时候,我的dom不会得到更新,我也不知道为什么。我曾经在我的杂货店组件
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;
}
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">×</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">×</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)
)