当前位置: 首页 > news >正文

第一个 Angular 项目 - 添加服务

第一个 Angular 项目 - 添加服务

这里主要用到的内容就是 [Angular 基础] - service 服务 提到的

前置项目在 第一个 Angular 项目 - 动态页面 这里查看

想要实现的功能是简化 shopping-listrecipe 之间的跨组件交流

回顾一下项目的结构:

❯ 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 EventEmitter<Ingredient[]>();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 EventEmitter<Recipe>();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 class="row"><div class="col-md-5"><app-recipe-list></app-recipe-list></div><div class="col-md-7"><app-recipe-detail[activeRecipe]="activeRecipe"*ngIf="activeRecipe; else noActiveRecipe"></app-recipe-detail><ng-template #noActiveRecipe><p>Please select a recipe to view the detailed information</p></ng-template></div>
    </div>
    
  • VM 层修改

    @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 class="row"><div class="col-xs-12"><button class="btn btn-success">New Recipe</button></div>
    </div>
    <hr />
    <div class="row"><div class="col-xs-12"><app-recipe-item*ngFor="let recipe of recipes"[recipe]="recipe"></app-recipe-item></div>
    </div>
    
  • VM 层修改如下

    @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="#"class="list-group-item clearfix"(click)="onSelectedRecipe()"[ngClass]="{ active: isActiveRecipe }"
    ><div class="pull-left"><h4 class="list-group-item-heading">{{ recipe.name }}</h4><p class="list-group-item-text">{{ recipe.description }}</p></div><span class="pull-right"><img[src]="recipe.imagePath"[alt]="recipe.name"class="image-responsive"style="max-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();}
    }
    

    这里变化稍微有一点多,主要也是针对 activeRecipeonSelectedRecipe 的修改。

    前者的判断我在 model 写了一个 isEqual 的方法用来判断名字、数量、图片等是否一样,当然只用这个方法的话还是有可能会出现数据碰撞的,因此写案例的时候我尽量不会用同一个名字去命名 ingredient。基于这个前提下,那么就可以判断当前的 recipe 是不是被选中的 recipe,同时添加 active 这一类名做更好的提示

    使用 subscribe 也是基于同样的理由,需要捕获 recipe 的变动

    onSelectedRecipe 的变化倒是没有太多,同样会触发一个事件,不过这个事件现在保存在 recipeService 中

    目前的实现是整个 recipe 都共享一个 service,因此这里 emit 的事件,在整个 recipe 组件下,只要 subscribe 了,就只会是同一个事件

recipe-detail 组件的修改

  • V 层

    <div class="row"><div class="col-xs-12"><imgsrc="{{ activeRecipe.imagePath }}"alt=" {{ activeRecipe.name }} "class="img-responsive"/></div>
    </div>
    <div class="row"><div class="col-xs-12"><h1>{{ activeRecipe.name }}</h1></div>
    </div>
    <div class="row"><div class="col-xs-12"><div class="btn-group" appDropdown><button type="button" class="btn btn-primary dropdown-toggle">Manage Recipe <span class="caret"></span></button><ul class="dropdown-menu"><li><a href="#" (click)="onAddToShoppingList()">To Shopping List</a></li><li><a href="#">Edit Recipe</a></li><li><a href="#">Delete Recipe</a></li></ul></div></div>
    </div>
    <div class="row"><div class="col-xs-12">{{ activeRecipe.description }}</div>
    </div>
    <div class="row"><div class="col-xs-12"><ul class="list-group"><liclass="list-group-item"*ngFor="let ingredient of activeRecipe.ingredients">{{ ingredient.name }} - {{ ingredient.amount }}</li></ul></div>
    </div>
    
  • VM 层

    @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 class="row"><div class="col-xs-10"><app-shopping-edit></app-shopping-edit><hr /><ul class="list-group"><aclass="list-group-item"style="cursor: pointer"*ngFor="let ingredient of ingredients">{{ ingredient.name }} ({{ ingredient.amount }})</a></ul></div>
    </div>
    
  • VM 层

    @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 class="row"><div class="col-xs-12"><form><div class="row"><div class="col-sm-5 form-group"><label for="name">Name</label><input type="text" id="name" class="form-control" #nameInput /></div><div class="col-sm-2 form-group"><label for="amount">Amount</label><inputtype="number"id="amount"class="form-control"#amountInput/></div></div><div class="row"><div class="col-xs-12"><div class="btn-toolbar"><buttonclass="btn btn-success mr-2"type="submit"(click)="onAddIngredient(nameInput)">Add</button><button class="btn btn-danger mr-2" type="button">Delete</button><button class="btn btn-primary" type="button">Edit</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 基本一致,也就不多赘述了

相关文章:

第一个 Angular 项目 - 添加服务

第一个 Angular 项目 - 添加服务 这里主要用到的内容就是 [Angular 基础] - service 服务 提到的 前置项目在 第一个 Angular 项目 - 动态页面 这里查看 想要实现的功能是简化 shopping-list 和 recipe 之间的跨组件交流 回顾一下项目的结构&#xff1a; ❯ tree src/app/…...

红日靶场3

靶场链接&#xff1a;漏洞详情 在虚拟机的网络编辑器中添加两个仅主机网卡 信息搜集 端口扫描 外网机处于网端192.168.1.0/24中&#xff0c;扫描外网IP端口&#xff0c;开放了80 22 3306端口 80端口http服务&#xff0c;可以尝试登录网页 3306端口mysql服务&#xff0c;可…...

B树的介绍

R-B Tree 简介特性B树特性m阶B树的性质&#xff08;这些性质是B树规定的&#xff09; B树的搜索B树的添加B树的删除——非叶子结点 简介 R-B Tree又称为Red-Black Tree&#xff0c;红黑树。是一种特殊的二叉查找树&#xff0c;红黑树的每个节点上都有存储为表示结点的颜色&…...

《The Art of InnoDB》第二部分|第4章:深入结构-磁盘结构-撕裂的页面(doublewrite buffer)

4.5 撕裂的页面 目录 4.5 撕裂的页面 4.5.1 双写缓冲区的作用 4.5.2 双写缓冲区的结构 4.5.3 双写缓冲区与Redolog的协同工作流程 4.5.2 双写缓冲区写入时机 4.5.3 禁用双写缓冲区 4.5.4 小结 未完待续... 上文我们学习了redo log的结构和其工作原理,它是一个…...

提示工程(Prompt Engineering)、微调(Fine-tuning) 和 嵌入(Embedding)

主要参考资料&#xff1a; 还没搞懂嵌入&#xff08;Embedding&#xff09;、微调&#xff08;Fine-tuning&#xff09;和提示工程&#xff08;Prompt Engineering&#xff09;&#xff1f;: https://blog.csdn.net/DynmicResource/article/details/133638079 B站Up主Nenly同学…...

【Flink精讲】Flink 内存管理

面临的问题 目前&#xff0c; 大数据计算引擎主要用 Java 或是基于 JVM 的编程语言实现的&#xff0c;例如 Apache Hadoop、 Apache Spark、 Apache Drill、 Apache Flink 等。 Java 语言的好处在于程序员不需要太关注底层内存资源的管理&#xff0c;但同样会面临一个问题&…...

正则化概念及使用

正则化概念及使用 正则化概念正则化原理常用的两种正则化方法1. L1 正则化&#xff08;Lasso&#xff09;2. L2 正则化&#xff08;Ridge&#xff09; 正则化参数 正则化概念 在机器学习中&#xff0c;我们致力于通过从训练数据中学习模式或规律来构建模型。为了找到最佳的模型…...

让程序员设计B端界面,好比武大郎招聘:向我看齐。不忍直视!

hello&#xff0c;我是大美B端工场&#xff0c;B端系统的要求越来越高了&#xff0c;很多公司还让程序员负责页面&#xff0c;页面搞的没法看&#xff0c;也怪不得程序员。程序员来搞页面&#xff0c;那还不是武大郎招聘——向我看齐&#xff0c;以我的标准为标准吗&#xff1f…...

使用python构建Android,探索跨平台应用开发Kivy框架

使用python构建Android&#xff0c;探索跨平台应用开发Kivy框架 1. 介绍Kivy框架 Kivy是什么&#xff1f; Kivy是一个开源的Python跨平台应用程序开发框架&#xff0c;旨在帮助开发者快速构建创新的、可扩展的移动应用和多点触控应用。Kivy采用MIT许可证&#xff0c;允许开发…...

08 Redis之集群的搭建和复制原理+哨兵机制+CAP定理+Raft算法

5 Redis 集群 2.8版本之前, Redis采用主从集群模式. 实现了数据备份和读写分离 2.8版本之后, Redis采用Sentinel哨兵集群模式 , 实现了集群的高可用 5.1 主从集群搭建 首先, 基本所有系统 , “读” 的压力都大于 “写” 的压力 Redis 的主从集群是一个“一主多从”的读写分…...

*MYSQL--索引--内部原理

MYSQL的索引根据功能,主要有三大类型: 1.HASH索引 2.二叉树 3.BTREE索引 一:HASH索引 1.内部原理: 在设置了某列为索引列之后,并且开始或者将要在相应索引列创建数据的时候,系统通过某种算法 F(X) 自动计算出来一个十六进制的哈希值,这个哈希值能够对应相应的字段值 所以…...

docker安装kafka和kafka-console-ui

3、安装kafka https://blog.csdn.net/m0_64210833/article/details/134199061 kafka依赖Zookeeper,当然也可以用内置的kraft。 安装前提条件 1.安装Zookeeper 1.1运行ZooKeeper容器 2.运行Kafka容器 2.1启动Kafka容器 3.验证 3.1进入Kafka容器 3.2查看容器状态 3.3查…...

Linux:gitlab创建组,创建用户,创建项目

创建组和项目 让后可以在组里创建一个个仓库 创建成员 我创建个成员再把他分配进这个组里 进入管理员 密码等会我们创建完用户再去配置密码 Regular是普通的用户&#xff0c;只可以正常去访问指定规则的项目 而下面的administrator就是管理员&#xff0c;可以随便进项目&…...

相机选型介绍

摄影测量中&#xff0c;相机是非常重要的角色&#xff0c;合适的相机产出合适的图像&#xff0c;得到合适的重建精度&#xff0c;这是相机的重要性。 您也许第一反应是&#xff0c;摄影测量所需的理想相机&#xff0c;是有着超高分辨率的相机&#xff0c;但事实可能并非如此&a…...

SQL创建数据库

SQL&#xff0c;全称结构化查询语言(Structured Query Language)&#xff0c;是一种用于管理关系型数据库的标准语言。通过 SQL&#xff0c;我们可以创建、查询、更新和删除数据库中的数据。今天&#xff0c;我们将学习使用SQL创建数据库。本文的目标是让读者了解如何使用SQL创…...

读书笔记-增强型分析:AI驱动的数据分析、业务决策与案例实践

目录 前言 运用人工智能技术&#xff0c;可以使人类社会变得更美好。人们总是期待产品更适合、服务更贴心、生活更便利。在实践中&#xff0c;技术给企业赋能&#xff0c;企业通过优质的产品和服务满足社会&#xff0c;提升人类福祉。很多金融企业已经开始尝试向潜在客户推送…...

NXP实战笔记(十):S32K3xx基于RTD-SDK在S32DS上配置CAN通信

目录 1、概述 2、SDK配置 2.1、配置目标 2.2、CAN配置 3、代码实现 4、测试结果 1、概述 S32K3xx的FlexCan与之前的S32K1xx很相似,Can的中断掩码寄存器(IMASK3)与中断标志位寄存器(IFLAG3)依赖于邮箱数。 FlexCan配置实例如下 FlexCan的整体图示如下 Protocol Engine…...

纳斯达克大屏-投放需要知道的几个条件-大舍传媒

引言 随着移动互联网的快速发展&#xff0c;数字广告媒体广告越来越受到企业的关注。纳斯达克大屏作为全球最大的数字媒体广告投放平台之一&#xff0c;拥有广泛的受众和优质的媒体资源&#xff0c;吸引了众多企业的眼球。要想在纳斯达克大屏上投放广告&#xff0c;企业需要了…...

python-可视化篇-简单-条形图输出主要省份GDP排名情况

条形图输出主要省份GDP排名情况 代码 gdp广东:97277.77:107671.07 江苏:92595.40:99631.52 山东:76469.70:71067.5 浙江:56197.00:62353 河南:48055.90:54259.2 四川:40678.10:46615.82 湖北:39366.60:45828.31 湖南:36425.78:39752.12 河北:36010.30:35104.5 福建:35804.04:…...

Sora - 探索AI视频模型的无限可能-官方报告解读与思考

一、引言 最近SORA火爆刷屏&#xff0c;我也忍不住找来官方报告分析了一下&#xff0c;本文将深入探讨OpenAI最新发布的Sora模型。Sora模型不仅仅是一个视频生成器&#xff0c;它代表了一种全新的数据驱动物理引擎&#xff0c;能够在虚拟世界中模拟现实世界的复杂现象。本文将重…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...