第一个 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-edit

11 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 层

    <a
      href="#"
      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">
        <img
          src="{{ 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">
          <li
            class="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">
          <a
            class="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>
              <input
                type="number"
                id="amount"
                class="form-control"
                #amountInput
              />
            </div>
          </div>
          <div class="row">
            <div class="col-xs-12">
              <div class="btn-toolbar">
                <button
                  class="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 基本一致,也就不多赘述了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/406146.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux---权限管理(ACL权限、特殊位和隐藏属性)

目录 1.ACT权限 1.1什么是ACT权限 1.2ACT图解 2.操作步骤 2.1添加测试目录、用户、组&#xff0c;并将用户添加到组 2.2修改目录的所有者和所属组 2.3设定权限 2.4为临时用户分配权限 2.4.1添加临时用户 2.4.2为临时用户分配特定权限 2.4.3查看目录权限&#xff0c;注…

【C语言】详解计算机二级c语言程序题

文章目录 前言资料相关程序题 一&#xff08;字符串&#xff09;程序题 二&#xff08;数组&#xff09;程序题 三&#xff08;基础&#xff09;程序题 四&#xff08;结构体&#xff09;程序题 五&#xff08;结构体&#xff09;程序题 六&#xff08;基础&#xff09; 前言 …

无人机精准定位技术,GPS差分技术基础,RTK原理技术详解

差分GPS的基本原理 差分GPS&#xff08;Differential GPS&#xff0c;简称DGPS&#xff09;的基本原理是利用一个或多个已知精确坐标的基准站&#xff0c;与用户&#xff08;移动站&#xff09;同时接收相同的GPS卫星信号。由于GPS定位时会受到诸如卫星星历误差、卫星钟差、大…

springboot+vue项目部署配置开机自启动

1.前端部属 下载nginx解压&#xff0c;在nginx\conf下找到nginx.conf 添加如下代码 server {listen 8081;server_name localhost;charset utf-8;location / {root F:/1ceshi/dist; #前端打包路径try_files $uri $uri/ /index.html;index index.html index.htm;}l…

123 Linux C++ 系统编程2 Linux 上安装卸载程序三种方法,linux 下解压缩命令 tar介绍。kill命令,top命令,umask 命令

一 通过命令和网络直接安装 sudo apt-get update sudo apt-get update 的工作就是将自己本地 ubutun的软件列表和 aliyun 的软件列表对比&#xff0c;如不一样&#xff0c;则更新。 sudo apt-get install 软件名 真正的安装 那么这里就有一个问题了&#xff0c; 怎么从aliy…

操作系统(1)——学习导论(Ⅰ)

目录 小程一言专栏链接: [link](http://t.csdnimg.cn/6grrU) 学习导论什么是操作系统主要功能强调 操作系统历史硬件层面处理器重要特点and功能 存储器磁盘I/O设备小程常用的I/O设备及其特点 小程一言 本操作系统专栏&#xff0c;是小程在学操作系统的过程中的第一步&#xff…

STL初始---C++

STL目录 1.STL的产生原因2.STL基本概念与六大组件2.1基本概念2.2六大组件 3.STL中容器、算法、迭代器3.1容器3.2算法3.3迭代器 4.STL简单应用4.1vector存放内置数据类型4.2vector存放自定义数据类型4.3vector容器嵌套容器 1.STL的产生原因 长久以来&#xff0c;软件界一直希望…

使用代理IP技术实现爬虫同步获取和保存

概述 在网络爬虫中&#xff0c;使用代理IP技术可以有效地提高爬取数据的效率和稳定性。本文将介绍如何在爬虫中同步获取和保存数据&#xff0c;并结合代理IP技术&#xff0c;以提高爬取效率。 正文 代理IP技术是一种常用的网络爬虫技术&#xff0c;通过代理服务器转发请求&a…

MDS300-16-ASEMI电源控制柜MDS300-16

编辑&#xff1a;ll MDS300-16-ASEMI电源控制柜MDS300-16 型号&#xff1a;MDS300-16 品牌&#xff1a;ASEMI 封装&#xff1a;M25 最大重复峰值反向电压&#xff1a;1600V 最大正向平均整流电流(Vdss)&#xff1a;300A 功率(Pd)&#xff1a;大功率 芯片个数&#xff1…

记录 使用FFMPEG 笔记本摄像头推流

一、使用 FFMPEG 测试摄像头拉流显示 # 获取摄像头名称 ffmpeg -list_devices true -f dshow -i dummy# 我笔记本上的摄像头名称如下 device_pnp_\\?\usb#vid_0408&pid_1020&mi_00#6&199e90f7&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global# 使…

基于JAVA的房屋出售出租系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 房屋销售模块2.2 房屋出租模块2.3 预定意向模块2.4 交易订单模块 三、系统展示四、核心代码4.1 查询房屋求租单4.2 查询卖家的房屋求购单4.3 出租意向预定4.4 出租单支付4.5 查询买家房屋销售交易单 五、免责说明 一、摘…

vue手写卡片切换,并且点击获取到卡片信息

需求&#xff1a;做一个卡片样式的列表&#xff0c;之后有一些基本信息&#xff0c;之后卡片选中后样式不一样&#xff0c;默认选中第一个卡片&#xff0c;点击卡片后可以获取到卡片的信息 一、效果 二、关键代码 index默认重0开始,activeTable默认为0,0-0等于0&#xff0c;但…

【Flink】FlinkSQL读取hive数据(批量)

一、简介: Hive在整个数仓中扮演了非常重要的一环,我们可以使用FlinkSQL实现对hive数据的读取,方便后续的操作,本次例子为Flink1.13.6版本 二、依赖jar包准备: 官网地址如下: Overview | Apache Flink 1、我们需要准备相关的jar包到Flink安装目录的lib目录下,我们需…

PostgreSQL如何使用UUID

离线安装时&#xff0c;一般有四个包&#xff0c;都安装的话&#xff0c;只需要开启uuid的使用即可&#xff0c;如果工具包(即 postgresql11-contrib&#xff09;没有安装的话&#xff0c;需要单独安装一次&#xff0c;再进行开启。 开启UUID方法 下面介绍一下如何开启&#…

第九节HarmonyOS 常用基础组件27-Rating

1、描述 提供在给定范围内选择评分的组件。 2、接口 Rating(options?:{rating:number, indicator?:boolean}) 3、参数 参数名 参数类型 必填 描述 rating number 是 设置并接收评分值。默认值&#xff1a;0&#xff1b;取值范围[0, stars]&#xff0c;小于0取0&am…

AIGC 实战:Ollama 和 Hugging Face 是什么关系?

Ollama和 Hugging Face 之间存在着双重关系&#xff1a; 1. Ollama是 Hugging Face 开发并托管的工具&#xff1a; Ollama是一个由 Hugging Face 自行开发的开源项目。它主要用于在本地运行大型语言模型 (LLM)&#xff0c;特别是存储在 GPT 生成的统一格式 (GPT-Generated Un…

com.alibaba.nacos.api.exception.NacosException: Request nacos server failed

问题描述 安装nacos2.0以上版本&#xff0c;启动报错:com.alibaba.nacos.api.exception.NacosException: Request nacos server failed com.alibaba.nacos.api.exception.NacosException: Request nacos server failed: at com.alibaba.nacos.client.naming.remote.gprc.Nami…

做抖音小店怎么选品?给新手商家的三条建议,能让你销量猛增999+

大家好&#xff0c;我是电商花花。 总是担心店铺不出单&#xff0c;没有销量&#xff0c;看着断断续续的收益&#xff0c;新手商家应该都是愁容满面吧。 今天花花从是3个维度上给新手商家一些建议&#xff0c;讲解一下如何高效选品&#xff0c;加你如何让你出单猛增999。 以前…

Java的编程之旅27——继承

1.继承的简介 继承是面向对象编程中的一个重要概念&#xff0c;指的是一个类可以继承另一个类的属性和方法。被继承的类称为父类或基类&#xff0c;继承这个父类的类称为子类或派生类。 通过继承&#xff0c;子类可以继承父类的属性和方法&#xff0c;使得子类具有相似的行为…

Java基于SSM+JSP的超市进销库存管理系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…