第一個 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 基本一致,也就不多贅述了

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/697514.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/697514.shtml
英文地址,請注明出處:http://en.pswp.cn/news/697514.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[概念區分] 正則表達式與正則化

正則表達式與正則化 機器學習在計算機科學和數據處理領域&#xff0c;關于“正則”的兩個術語&#xff1a;正則表達式和正則化&#xff0c;雖然它們在名稱上非常相似&#xff0c;但實際上它們是完全不同的概念。 正則表達式 也被稱為 regex&#xff0c;是一種強大的工具&…

Linux freezer機制

一、概述 系統進入suspended或進程被加入到cgroup凍結或解凍分組&#xff0c;用戶進程和部分內核線程被凍結后&#xff0c;會剝奪執行cpu資源&#xff0c;解凍或喚醒后恢復正常。 二、進程凍結與解凍原理 2.1 進程凍結 用戶進程和內核線程凍結的基本流程&#xff1a; 內核態…

設計模式-建造者模式(Builder Pattern)

一、建造者模式說明 建造者模式&#xff08;Builder Pattern&#xff09;是一種創建型設計模式&#xff0c;它的主要目的是將一個復雜對象的構建過程與其表示分離&#xff0c;使得同樣的構建過程可以創建不同的表示。 在建造者模式中&#xff0c;通常涉及以下幾個角色&#xf…

多業務場景下對于redis分布式鎖的一些思考

現在讓你寫一個Redis分布式鎖 大概率你會先寫一個框架 public Boolean setIfAbsent(String key, Object value,Long timeout) {try {return Boolean.TRUE.equals(objectRedisTemplate.opsForValue().setIfAbsent(key, value,timeout,TimeUnit.SECONDS));} catch (Exception e) …

2024開年,手機廠商革了自己的命

文&#xff5c;劉俊宏 編&#xff5c;王一粟 2024開年&#xff0c;AI終端的號角已經由手機行業吹響。 OPPO春節期間就沒閑著&#xff0c;首席產品官劉作虎在大年三十就迫不及待地宣布&#xff0c;OPPO正式進入AI手機時代。隨后在開年后就緊急召開了AI戰略發布會&#xff0c;…

【Antd】Form 表單獲取不到 Input 的值

文章目錄 今天遇到了一個奇怪的bug&#xff0c;Form表單中的Input組件的值&#xff0c;不能被Form獲取&#xff0c;導致輸入了內容&#xff0c;但是表單提交的時候值為undefined 報錯代碼 import { Button, Form, Input } from antd; import React from react;const App: Rea…

GaussDB SQL調優:建立合適的索引

背景 GaussDB是華為公司傾力打造的自研企業級分布式關系型數據庫&#xff0c;該產品具備企業級復雜事務混合負載能力&#xff0c;同時支持優異的分布式事務&#xff0c;同城跨AZ部署&#xff0c;數據0丟失&#xff0c;支持1000擴展能力&#xff0c;PB級海量存儲等企業級數據庫…

SQL中為什么不要使用1=1

最近看幾個老項目的SQL條件中使用了11&#xff0c;想想自己也曾經這樣寫過&#xff0c;略有感觸&#xff0c;特別拿出來說道說道。 編寫SQL語句就像炒菜&#xff0c;每一種調料的使用都可能會影響菜品的最終味道&#xff0c;每一個SQL條件的加入也可能會影響查詢的執行效率。那…

昨天Google發布了最新的開源模型Gemma,今天我來體驗一下

前言 看看以前寫的文章&#xff0c;業余搞人工智能還是很早之前的事情了&#xff0c;之前為了高工資&#xff0c;一直想從事人工智能相關的工作都沒有實現。現在終于可以安靜地系統地學習一下了。也是一邊學習一邊寫博客記錄吧。 昨天Google發布了最新的開源模型Gemma&#xf…

電商數據采集的幾個標準

面對體量巨大的電商數據&#xff0c;很多品牌會選擇對自己有用的數據進行分析&#xff0c;比如在控價過程中&#xff0c;需要對商品的價格數據進行監測&#xff0c;或者是需要做數據分析時&#xff0c;則需要采集到商品的價格、銷量、評價量、標題、店鋪名等信息&#xff0c;數…

Unity中.Net與Mono的關系

什么是.NET .NET是一個開發框架&#xff0c;它遵循并采用CIL(Common Intermediate Language)和CLR(Common Language Runtime)兩種約定&#xff0c; CIL標準為一種編譯標準&#xff1a;將不同編程語言&#xff08;C#, JS, VB等&#xff09;使用各自的編譯器&#xff0c;按照統…

JavaScript 原始值和引用值在變量復制時的異同

相比于其他語言&#xff0c;JavaScript 中的變量可謂獨樹一幟。正如 ECMA-262 所規定的&#xff0c;JavaScript 變量是松散類型的&#xff0c;而且變量不過就是特定時間點一個特定值的名稱而已。由于沒有規則定義變量必須包含什么數據類型&#xff0c;變量的值和數據類型在腳本…

mysql.service is not a native service, redirecting to systemd-sysv-install

字面意思&#xff1a;mysql.service不是本機服務&#xff0c;正在重定向到systemd sysv安裝 在CentOS上使用Systemd管理MySQL服務的具體步驟如下&#xff1a; 1、創建MySQL服務單元文件&#xff1a; 首先&#xff0c;你需要創建一個Systemd服務單元文件&#xff0c;以便Syste…

【Python筆記-設計模式】原型模式

一、說明 原型模式是一種創建型設計模式&#xff0c; 用于創建重復的對象&#xff0c;同時又能保證性能。 使一個原型實例指定了要創建的對象的種類&#xff0c;并且通過拷貝這個原型來創建新的對象。 (一) 解決問題 主要解決了對象的創建與復制過程中的性能問題。主要針對…

redhawk:使用ipf文件反標instance power

我正在「拾陸樓」和朋友們討論有趣的話題,你?起來吧? 拾陸樓知識星球入口 往期文章鏈接: Redhawk:Input Data Preparation 使用ptpx和redhawk報告功耗時差別總是很大,如果需要反標top/block的功耗值可以在gsr文件中使用BLOCK_POWER_FOR_SCALING的命令

Verilog刷題筆記35

題目&#xff1a; Create a 1-bit wide, 256-to-1 multiplexer. The 256 inputs are all packed into a single 256-bit input vector. sel0 should select in[0], sel1 selects bits in[1], sel2 selects bits in[2], etc. 解法&#xff1a; module top_module( input [255:…

Spring Cloud Alibaba-05-Gateway網關-02-斷言(Predicate)使用

Lison <dreamlison163.com>, v1.0.0, 2023.10.20 Spring Cloud Alibaba-05-Gateway網關-02-斷言(Predicate)使用 文章目錄 Spring Cloud Alibaba-05-Gateway網關-02-斷言(Predicate)使用通過時間匹配通過 Cookie 匹配通過 Header 匹配通過 Host 匹配通過請求方式匹配通…

C# CAD2016 cass10宗地Xdata數據寫入

一、 查看cass10寫入信息 C# Cad2016二次開發獲取XData信息&#xff08;二&#xff09; 一共有81條數據 XData value: QHDM XData value: 121321 XData value: SOUTH XData value: 300000 XData value: 141121JC10720 XData value: 權利人 XData value: 0702 XData value: YB…

2.居中方式總結

居中方式總結 經典真題 怎么讓一個 div 水平垂直居中 盒子居中 首先題目問到了如何進行居中&#xff0c;那么居中肯定分 2 個方向&#xff0c;一個是水平方向&#xff0c;一個是垂直方向。 水平方向居中 水平方向居中很簡單&#xff0c;有 2 種常見的方式&#xff1a; 設…

java面試題之mybatis篇

什么是ORM&#xff1f; ORM&#xff08;Object/Relational Mapping&#xff09;即對象關系映射&#xff0c;是一種數據持久化技術。它在對象模型和關系型數據庫直接建立起對應關系&#xff0c;并且提供一種機制&#xff0c;通過JavaBean對象去操作數據庫表的數據。 MyBatis通過…