杭州网站艰涩,建设银行招标网站首页,杭州做网站外包公司有哪些,wordpress视频教第一个 Angular 项目 - 添加服务
这里主要用到的内容就是 [Angular 基础] - service 服务 提到的
前置项目在 第一个 Angular 项目 - 动态页面 这里查看
想要实现的功能是简化 shopping-list 和 recipe 之间的跨组件交流
回顾一下项目的结构#xff1a;
❯ tree src/app/…第一个 Angular 项目 - 添加服务
这里主要用到的内容就是 [Angular 基础] - service 服务 提到的
前置项目在 第一个 Angular 项目 - 动态页面 这里查看
想要实现的功能是简化 shopping-list 和 recipe 之间的跨组件交流
回顾一下项目的结构
❯ tree src/app/
src/app/
├── directives
├── header
├── recipes
│ ├── recipe-detail
│ ├── recipe-list
│ │ ├── recipe-item
│ ├── recipe.model.ts
├── shared
│ └── ingredient.model.ts
└── shopping-list├── shopping-edit11 directories, 31 files层级结构相对来说还是有一点点复杂的所以如果在 app 层构建一个对应的变量和事件再一层层往下传无疑是一件非常麻烦的事情(尤其 V 层和 VM 层都要进行事件传输的对应变化)而使用 service 就能相对而言比较简单的解决这个问题
创建新的 service
这里主要会创建两个 services
src/app/
├── services
│ ├── ingredient.service.ts
│ └── recipe.service.ts一个用来管理所有的 ingredients——这部分是放在 shopping-list 中进行展示的另一个就是管理所有的 recipes
ingredient service
实现代码如下
Injectable({providedIn: root,
})
export class IngredientService {ingredientChanged new EventEmitterIngredient[]();private ingredientList: Ingredient[] [new Ingredient(Apples, 5),new Ingredient(Tomatoes, 10),];constructor() {}get ingredients() {return this.ingredientList.slice();}addIngredient(Ingredient: Ingredient) {this.ingredientList.push(Ingredient);this.ingredientChanged.emit(this.ingredients);}addIngredients(ingredients: Ingredient[]) {this.ingredientList.push(...ingredients);this.ingredientChanged.emit(this.ingredients);}
}代码分析如下 Injectable 这里使用 providedIn: root 是因为我想让所有的组件共享一个 service这样可以满足当 ingredient 页面修改对应的食材并且将其发送到 shopping-list 的时候数据可以进行同步渲染 ingredientChanged 这是一个 event emitter主要的目的就是让其他的组件可以 subscribe 到事件的变更 subscribe 是之前的 service 笔记中没提到的内容这里暂时不会细舅不过会放一下用法 get ingredients() 一个语法糖这里的 slice 会创造一个 shallow copy防止意外对数组进行修改 也可以用 lodash 的 cloneDeep或者单独创建一个函数去进行深拷贝 add 函数 向数组中添加元素并向外发送数据变更的信号
recipe service
Injectable()
export class RecipeService {private recipeList: Recipe[] [new Recipe(Recipe 1, Description 1, http://picsum.photos/200/200, [new Ingredient(Bread, 5),new Ingredient(Ginger, 10),]),new Recipe(Recipe 2, Description 2, http://picsum.photos/200/200, [new Ingredient(Chicken, 10),new Ingredient(Bacon, 5),]),];private currRecipe: Recipe;recipeSelected new EventEmitterRecipe();get recipes() {return this.recipeList.slice();}get selectedRecipe() {return this.currRecipe;}
}这里主要讲一下 Injectable因为 recipe service 的部分应该被限制在 recipe 这个组件下所以这里不会采用 singleton 的方式实现
其余的实现基本和上面一样
修改 recipe
这里依旧是具体业务具体分析 recipe 这里需要获取 activeRecipe ngIf 去渲染 recipe-detail 部分的内容如 没有选中 recipe选中了 recipe recipe-detail 这里需要 activeRecipe 去渲染对应的数据如上图 recipe-list 这里需要 recipes 去完成循环渲染对应的 recipe-item recipe-item 这里需要 activeRecipe 完成对 active 这个 class 的添加
recipe 组件的修改 V 层修改 div classrowdiv classcol-md-5app-recipe-list/app-recipe-list/divdiv classcol-md-7app-recipe-detail[activeRecipe]activeRecipe*ngIfactiveRecipe; else noActiveRecipe/app-recipe-detailng-template #noActiveRecipepPlease select a recipe to view the detailed information/p/ng-template/div
/divVM 层修改 Component({selector: app-recipes,templateUrl: ./recipes.component.html,providers: [RecipeService],
})
export class RecipesComponent implements OnInit, OnDestroy {activeRecipe: Recipe;constructor(private recipeService: RecipeService) {}ngOnInit() {this.recipeService.recipeSelected.subscribe((recipe: Recipe) {this.activeRecipe recipe;});}ngOnDestroy(): void {this.recipeService.recipeSelected.unsubscribe();}
}这里主要是对 V 层进行了一些修改减少了一些数据绑定。大多数的用法这里都是之前在 service 的笔记中提到的除了这个 subscribe 的使用
简单的说在 subscribe 之后每一次 event 触发后在这个 subscription 里它都可以获取 event 中传来的信息并进行对应的更新操作
recipe-list 组件的修改 V 层修改如下 div classrowdiv classcol-xs-12button classbtn btn-successNew Recipe/button/div
/div
hr /
div classrowdiv classcol-xs-12app-recipe-item*ngForlet recipe of recipes[recipe]recipe/app-recipe-item/div
/divVM 层修改如下 Component({selector: app-recipe-list,templateUrl: ./recipe-list.component.html,styleUrl: ./recipe-list.component.css,
})
export class RecipeListComponent implements OnInit {recipes: Recipe[];constructor(private recipeService: RecipeService) {}ngOnInit() {this.recipes this.recipeService.recipes;}
}这里主要就是获取数据的方式变了也不需要向下传递 Input向上触发 Output 了
reccipe-item 组件的修改 V 层 ahref#classlist-group-item clearfix(click)onSelectedRecipe()[ngClass]{ active: isActiveRecipe }
div classpull-lefth4 classlist-group-item-heading{{ recipe.name }}/h4p classlist-group-item-text{{ recipe.description }}/p/divspan classpull-rightimg[src]recipe.imagePath[alt]recipe.nameclassimage-responsivestylemax-height: 50px//span
/a这里做的另外一个修改就是把 a 标签移到了 list-item 去处理这样语义化相对更好一些 VM 层 Component({selector: app-recipe-item,templateUrl: ./recipe-item.component.html,styleUrl: ./recipe-item.component.css,
})
export class RecipeItemComponent implements OnInit, OnDestroy {Input() recipe: Recipe;isActiveRecipe false;constructor(private recipeService: RecipeService) {}ngOnInit() {this.recipeService.recipeSelected.subscribe((recipe: Recipe) {this.isActiveRecipe recipe.isEqual(this.recipe);});}onSelectedRecipe() {this.recipeService.recipeSelected.emit(this.recipe);}ngOnDestroy(): void {this.recipeService.recipeSelected.unsubscribe();}
}这里变化稍微有一点多主要也是针对 activeRecipe 和 onSelectedRecipe 的修改。 前者的判断我在 model 写了一个 isEqual 的方法用来判断名字、数量、图片等是否一样当然只用这个方法的话还是有可能会出现数据碰撞的因此写案例的时候我尽量不会用同一个名字去命名 ingredient。基于这个前提下那么就可以判断当前的 recipe 是不是被选中的 recipe同时添加 active 这一类名做更好的提示 使用 subscribe 也是基于同样的理由需要捕获 recipe 的变动 onSelectedRecipe 的变化倒是没有太多同样会触发一个事件不过这个事件现在保存在 recipeService 中 目前的实现是整个 recipe 都共享一个 service因此这里 emit 的事件在整个 recipe 组件下只要 subscribe 了就只会是同一个事件
recipe-detail 组件的修改 V 层 div classrowdiv classcol-xs-12imgsrc{{ activeRecipe.imagePath }}alt {{ activeRecipe.name }} classimg-responsive//div
/div
div classrowdiv classcol-xs-12h1{{ activeRecipe.name }}/h1/div
/div
div classrowdiv classcol-xs-12div classbtn-group appDropdownbutton typebutton classbtn btn-primary dropdown-toggleManage Recipe span classcaret/span/buttonul classdropdown-menulia href# (click)onAddToShoppingList()To Shopping List/a/lilia href#Edit Recipe/a/lilia href#Delete Recipe/a/li/ul/div/div
/div
div classrowdiv classcol-xs-12{{ activeRecipe.description }}/div
/div
div classrowdiv classcol-xs-12ul classlist-groupliclasslist-group-item*ngForlet ingredient of activeRecipe.ingredients{{ ingredient.name }} - {{ ingredient.amount }}/li/ul/div
/divVM 层 Component({selector: app-recipe-detail,templateUrl: ./recipe-detail.component.html,styleUrl: ./recipe-detail.component.css,
})
export class RecipeDetailComponent {Input() activeRecipe: Recipe;constructor(private ingredientService: IngredientService) {}onAddToShoppingList() {this.ingredientService.addIngredients(this.activeRecipe.ingredients);}
}这里通过调用 ingredient service 将当前 recipe 中的 ingredient 送到 shopping-list 的 view 下效果如下 这里没有做 unique key 的检查而且实现是通过 Array.push 去做的因此只会无限增加而不是更新已有的元素。不过大致可以看到这个跨组件的交流是怎么实现的
修改 shopping-list
这里的实现和 recipe 差不多就只贴代码了
shopping-list 组件的修改 V 层 div classrowdiv classcol-xs-10app-shopping-edit/app-shopping-edithr /ul classlist-groupaclasslist-group-itemstylecursor: pointer*ngForlet ingredient of ingredients{{ ingredient.name }} ({{ ingredient.amount }})/a/ul/div
/divVM 层 Component({selector: app-shopping-list,templateUrl: ./shopping-list.component.html,styleUrl: ./shopping-list.component.css,
})
export class ShoppingListComponent implements OnInit, OnDestroy {ingredients: Ingredient[] [];constructor(private ingredientService: IngredientService) {}ngOnInit(): void {this.ingredients this.ingredientService.ingredients;this.ingredientService.ingredientChanged.subscribe((ingredients: Ingredient[]) {this.ingredients ingredients;});}ngOnDestroy(): void {this.ingredientService.ingredientChanged.unsubscribe();}
}同样也是一个 subscription 的实现去动态监听 ingredients 的变化
shopping-edit 组件的修改 V 层 div classrowdiv classcol-xs-12formdiv classrowdiv classcol-sm-5 form-grouplabel fornameName/labelinput typetext idname classform-control #nameInput //divdiv classcol-sm-2 form-grouplabel foramountAmount/labelinputtypenumberidamountclassform-control#amountInput//div/divdiv classrowdiv classcol-xs-12div classbtn-toolbarbuttonclassbtn btn-success mr-2typesubmit(click)onAddIngredient(nameInput)Add/buttonbutton classbtn btn-danger mr-2 typebuttonDelete/buttonbutton classbtn btn-primary typebuttonEdit/button/div/div/div/form/div
/div这里添加了一个按钮的功能实现添加 ingredient VM 层 Component({selector: app-shopping-edit,templateUrl: ./shopping-edit.component.html,styleUrl: ./shopping-edit.component.css,
})
export class ShoppingEditComponent {ViewChild(amountInput, { static: true })amountInput: ElementRef;constructor(private ingredientService: IngredientService) {}onAddIngredient(nameInput: HTMLInputElement) {this.ingredientService.addIngredient(new Ingredient(nameInput.value, this.amountInput.nativeElement.value));}
}这里的 onAddIngredient 实现方式和添加整个 list 基本一致也就不多赘述了