第一个 Angular 项目 - 动态页面

第一个 Angular 项目 - 动态页面

使用的所有技巧都在下面的笔记里:

  • [Angular 基础] - 数据绑定(databinding)

  • [Angular 基础] - 指令(directives)

    以上为静态页面,即不涉及到跨组件交流的内容

    以下涉及到组件内的沟通,从这开始数据就“活”了

  • [Angular 基础] - 自定义事件 & 自定义属性

  • [Angular 基础] - 视图封装 & 局部引用 & 父子组件中内容传递

  • [Angular 基础] - 生命周期函数


静态页面的实现在这里:第一个 Angular 项目 - 静态页面

这篇笔记结合新学的绑定知识,盘活数据

拟态路由

Angular 有对 routing 的 built-in 支持,鉴于这里暂时还没有学到 routing,所以这里用的是拟态的方式去实现。这里的需求是 3 个点:

  1. 导航栏有一个对所处的页面进行高亮

  2. 切换到 recipe 页面只显示 recipe 的内容

  3. 切换到 shopping-list 页面只显示 shopping list 相关的内容

接下来对功能的实现进行分析:

  • 需要在 header 的 VM 层中保存一个变量,这个变量名用以动态修改 ngClass,并在所处的页面添加 active 这一 class 名

  • header 的 VM 层需要使用 @Output 这个指令去创建一个 EventEmitter,传输的对象为当前所处的页面

  • app 的 V 层和 VM 层需要动态接受从子组件传来的事件,并且动态渲染对应页面的内容

    动态渲染的部分可以用 ngIf 或是 ngSwitch 实现

分析完了后就可以开始实现了

header

header V 层

这里的修改相对比较简单,主要是 ngClass 的修改,绑定 click 事件这两个,这部分的内容都可以在 [Angular 基础] - 指令(directives) 中查看,修改如下:

<ul class="nav navbar-nav"><li [ngClass]="{ active: activeTab === 'recipe' }"><a href="#" (click)="onClickTab('recipe')">Recipes</a></li><li [ngClass]="{ active: activeTab === 'shopping-list' }"><a href="#" (click)="onClickTab('shopping-list')">Shopping List</a></li>
</ul>
header VM 层

这里主要修改的部分就是:

  • 新建一个变量保存 activeTab
  • 新建一个 EventEmitter 向父组件传输当前的 activeTab
  • 新建一个 clickHandler 去实现上面两个操作

具体代码如下:

export class HeaderComponent {@Output() activeTabChanged = new EventEmitter<string>();activeTab: string = 'recipe';onClickTab(activeTab: string) {this.activeTab = activeTab;this.activeTabChanged.emit(activeTab);}
}

EventEmitter 部分的实现可以查看这篇笔记:[Angular 基础] - 自定义事件 & 自定义属性

app 路由修改

app 路由 V 层修改

V 层的修改相对比较简单,主要是需要将 EventEmitter 绑定一下,并且实现一下条件控制,我这里用 ngSwitch 实现:

<app-header (activeTabChanged)="onTabChange($event)"></app-header><div class="container"><div class="row"><div class="col-md-12"><div [ngSwitch]="activeTab"><app-recipes *ngSwitchCase="'recipe'"></app-recipes><app-shopping-list *ngSwitchCase="'shopping-list'"></app-shopping-list></div></div></div>
</div>
app 路由 VM 层修改

VM 层实现的功能也差不多,新建一个变量保存 activeTab,随后实现对应的 onTabChange 去修改 activeTab,实现如下:

export class AppComponent {title = 'recipe-book';activeTab = 'recipe';onTabChange($event: string) {this.activeTab = $event;}
}

最后实现的效果如下:

在这里插入图片描述

recipe-list 清理

recipe-list 部分要清理的比较多:

  • 将单独的 recipe 从 recipe-list 传到 recipe-item 中

    刚开始写的时候还没有涉及到组件之间的沟通,因此子组件无法获取父组件的数据

    现在父子组件已经可以沟通了,那么还是要遵从一下 SRP 的

  • 点击列表中的 recipe,右侧能够显示出 recipe 的具体信息

    这部分就像 wireframe 中的设定一样:

    在这里插入图片描述

传递 recipe 到子组件

recipe-list V 层清理
<a href="#" class="list-group-item clearfix" *ngFor="let recipe of recipes"><app-recipe-item [recipe]="recipe"></app-recipe-item>
</a>

这里原本的内容,包括渲染名称、描述、图片都会移到 recipe-item 中去

recipe-item V 层修改

这里主要就是接受之前在 recipe-list V 层的模板

<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>
recipe-item VM 层修改

这里通过 @Input 去接受父组件传来的数据以完成动态渲染,代码如下:

export class RecipeItemComponent {@Input() recipe: Recipe;
}

这里看到样式没有任何的变化,不过 recipe 的渲染部分是由 recipe-item 完成的:

在这里插入图片描述

选择当前展示 recipe

这部分的实现其实和 navigation 的实现差不多,逻辑也基本一样。

看一下目前的项目结构:

❯ tree src/app/
src/app/
├── recipes
│   ├── recipe-detail
│   ├── recipe-list
│   │   ├── recipe-item

只不过这里处理 Output 的是 recipe-list,接受子组件传来的参数是 recipe,并且 recipe 需要将传过来的数据再传递到 recipe-detail 进行展示

⚠️:我这里偷了个懒,直接从 recipe-list 开始往上走。原本的写法应该是所有的东西都放在 recipe-item 里,然后一个个 emit event,因为 Angular 默认情况下是阻止 event propagation 的,所以只能手动一个个往上送(bubbling)

recipe-list V 层添加点击事件
<ahref="#"class="list-group-item clearfix"*ngFor="let recipe of recipes"[ngClass]="{ active: recipe === selectedRecipe }"(click)="onSelectRecipe(recipe)"
><app-recipe-item [recipe]="recipe"></app-recipe-item>
</a>

这里的 a 标签完全是可以丢到 app-recipe-item 去实现的,不过事件一旦传到了 app-recipe-item,就新创建了一个父子之间的沟通,所以需要从 app-recipe-item 再向上 propagate 一个事件,所以这里偷懒了

recipe-list VM 层添加选择事件

这里是对 onSelectRecipe 进行的处理,这里本身也是一个事件的中转站,它需要向 recipes 这一层去发送事件,使得当前被选中的对象可以传到 recipe-detail 中被展示:

export class RecipeListComponent {selectedRecipe: Recipe = this.recipes[0];@Output() activeRecipeChanged = new EventEmitter<Recipe>();ngOnInit() {this.activeRecipeChanged.emit(this.recipes[0]);}onSelectRecipe(recipe: Recipe) {this.selectedRecipe = recipe;this.activeRecipeChanged.emit(recipe);}
}

💡:我这里是用了 ngOnInit 在组件渲染时自动旋转数组中第一个对象,这个根据业务条件设置,也可以不选择这种做法。

recipe V 层添加绑定事件

这里除了新增事件绑定之外,还需要将绑定的对象传到 recipe-detail 中去,主要修改如下:

<div class="row"><div class="col-md-5"><app-recipe-list(activeRecipeChanged)="onRecipeChange($event)"></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 create and select a recipe to view the detailed information</p></ng-template></div>
</div>

这里使用了 ngIf 进行条件控制,预防的是一个边界条件,也就是当数组为空时,recipes[0] 会是一个 undefined,这样会影响 recipe-detail 的后续操作

recipe VM 层添加接受修改数据

VM 层变动不大,主要是接受传递的事件,然后保存传上来的 recipe 即可:

export class RecipesComponent {activeRecipe: Recipe;onRecipeChange($event: Recipe) {this.activeRecipe = $event;}
}
recipe-detail

这里主要就是用 @Input 接受一下传来的数据,在 V 层渲染出来即可,变动比较少,代码就都贴下面了,V 层除了变量名基本没有变动:

export class RecipeDetailComponent {@Input() activeRecipe: Recipe;
}
<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"><button type="button" class="btn btn-primary dropdown-toggle">Manage Recipe <span class="caret"></span></button><ul class="dropdown-menu"><li><a href="#">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">Ingredients</div>
</div>

最后效果如下:

在这里插入图片描述

⚠️:后来发现图片的路径没放进去,HTML 部分更新了,但是动图没更新

shopping-list

这里的应用主要是 local reference 和 @ViewChild,wireframe 中看到有两个输入:

在这里插入图片描述

所以这里两种方式都会用上

导入 FormsModule

不导入 FormsModule 所有的实现都会以默认的 HTML 实现,这里的 button 类型是 submit,也就是说会触发一个提交的 action,一般情况下会导致页面重新刷新

导入 FormsModule 会让 Angular 接收所有传来的参数,并且让 Angular 癌性决定后面的操作

shopping-list V 层修改

这里主要修改的部分就是在两个 input 中加入了 local reference,并且在 submit 的 button 那里绑定了点击时间,并且将 nameInput 作为 reference 传了过去

<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"><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>
shopping-list VM 层修改

这里比较简单,使用 @ViewChild 绑定当前 V 层的 local reference,并且接受点击事件传来的 #nameInput,最后按照老规矩,创建一个 EventEmitter 让父组件监听:

export class ShoppingEditComponent {@ViewChild('amountInput', { static: true })amountInput: ElementRef;@Output() ingredientAdded = new EventEmitter<Ingredient>();onAddIngredient(nameInput: HTMLInputElement) {this.ingredientAdded.emit({name: nameInput.value,amount: this.amountInput.nativeElement.value,});}
}
shopping-cart 接受子组件传来的参数

这里也比较简单,就放在一起写了

<div class="row"><div class="col-xs-10"><app-shopping-edit(ingredientAdded)="onAddIngredient($event)"></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>
import { Component } from '@angular/core';
import { Ingredient } from '../shared/ingredient.model';@Component({selector: 'app-shopping-list',templateUrl: './shopping-list.component.html',styleUrl: './shopping-list.component.css',
})
export class ShoppingListComponent {ingredients: Ingredient[] = [new Ingredient('Apples', 5),new Ingredient('Tomatoes', 10),];onAddIngredient(ingredient: Ingredient) {this.ingredients.push(ingredient);}
}

最后完成效果如下:

在这里插入图片描述


至此一个功能大抵完成的动态页面就写完了

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

才气系统与逻辑系统道装实现的比较

才气系统与逻辑系统道装实现的比较 道装道装思想简介烛火流形学习引擎&#xff0c;流形学习的引入王船山信息熵&#xff0c;简称王船山熵&#xff1b;凝聚态数学可计算函数科学方法道装由来琴语言简介逻辑与才气的逐层比较表格&#xff08;王船山熵&#xff09; 道装 道装思想…

OpenCV 入门讲解

OpenCV 入门讲解 OpenCV&#xff08;Open Source Computer Vision Library&#xff09; 是一个开源的计算机视觉库&#xff0c;它提供了许多高效实现计算机视觉算法的函数&#xff0c;从基本的滤波到高级的物体检测都有涵盖。OpenCV 使用 C/C 开发&#xff0c;同时也提供了 Pyt…

短链接系统测试报告

目录 项目背景 项目功能 自动化测试 总结 项目背景 随着互联网的发展&#xff0c;链接&#xff08;URL&#xff09;变得越来越长且复杂&#xff0c;这不仅影响用户体验&#xff0c;还可能由于字符限制导致在某些平台或应用中无法完整显示。为了解决这一问题&#xff0c;我…

Ubuntu22.04LTS编译Frida历史版本,环境配制及细节调整

经常使用Frida的朋友们可能会遇到Frida的各种问题需要自定义的&#xff0c;而这时候Frida的本地编译就显得很重要了。 最近一位朋友发现使用Frida14/15/16版的server只能连拉一定数量的设备&#xff0c;超过了frida-device-manager便不能连接设备。 实现没有办法&#xff0c;…

综合练习

目录 查询每个员工的编号、姓名、职位、基本工资、部门名称、部门位置 确定要使用的数据表 确定已知的关联字段 查询每个员工的编号、姓名、职位、基本工资、工资等级 确定要使用的数据表 确定已知的关联字段 查询每个员工的编号、姓名、职位、基本工资、部门名称、工资…

⭐北邮复试刷题589. N 叉树的前序遍历__DFS (力扣每日一题)

589. N 叉树的前序遍历 给定一个 n 叉树的根节点 root &#xff0c;返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示&#xff0c;每组子节点由空值 null 分隔&#xff08;请参见示例&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [1,null,…

数据安全之认识数据资产管理平台

文章目录 一、什么是数据资产二、什么是数据资产管理平台1、什么是数据资产管理平台2、为什么需要数据资产管理平台 三、数据资产管理平台的主要功能四、数据资产管理平台的工作原理五、数据资产管理平台的应用场景六、安全资产管理平台与数据资产管理平台的区别与关系1、安全资…

华为配置旁挂二层组网直接转发示例

配置旁挂二层组网直接转发示例 组网图形 图1 配置旁挂二层组网直接转发示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件扩展阅读 业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff…

图像处理之《隐写网络的隐写术》论文阅读

一、文章摘要 隐写术是一种在双方之间进行秘密通信的技术。随着深度神经网络(DNN)的快速发展&#xff0c;近年来越来越多的隐写网络被提出&#xff0c;并显示出良好的性能。与传统的手工隐写工具不同&#xff0c;隐写网络的规模相对较大。如何在公共信道上秘密传输隐写网络引起…

anomalib1.0学习纪实-续2:三个文件夹

为了读懂程序&#xff0c;有三个最重要的文件夹&#xff0c;如下图&#xff1a; 正好对应四个类&#xff0c;如下图&#xff1a; 三个类的来源如下图所示&#xff1a; 注意&#xff0c;MVTec是个大类&#xff0c;里面用到了这里的第四个类MVTecDataset&#xff0c;代码如下。…

洛谷: P1553 数字反转(升级版)

思路: 没想到什么好办法&#xff0c;一步一步来。整体就是反转&#xff0c;删除前导/后导0&#xff0c;反转&#xff0c;删除前导/后导0。 第一次AC没过去&#xff0c;原因是没考虑到分数的分母前导0的情况&#xff0c;比如1234567890/1234567890这个样例&#xff0c;结果输出…

蓝桥杯官网填空题(寻找整数)

问题描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 有一个不超过 10^17 的正整数 n&#xff0c;知道这个数除以 2 至 49 后的余数如下表所示&#xff0c;求这个正整数最小是多少。 运行限制 最大运行时间&#xff1a;…

【软考问题】-- 10 - 知识精讲 - 项目风险管理

一、基本问题 1&#xff1a;按照可预测性&#xff0c;风险分哪三类&#xff1f; &#xff08;1&#xff09;已知风险&#xff1a;如项目目标不明确&#xff0c; 过分乐观的进度计划&#xff0c; 设计或施工变更和材料价格波动等。&#xff08;2&#xff09;可预测风险&#xff…

智慧城市与数字孪生:实现城市可持续发展的关键

一、引言 随着全球城市化进程的加速&#xff0c;城市面临着诸多挑战&#xff0c;如资源紧张、环境恶化、交通拥堵等。为了解决这些问题&#xff0c;智慧城市的概念应运而生。智慧城市利用先进的信息通信技术&#xff0c;提升城市治理水平&#xff0c;改善市民的生活质量。而数…

一、直方图相关学习

1、灰度直方图 1.1 基本概念和作用 表示图像中每个灰度级别的像素数量。用于分析图像的亮度分布情况。 1.2 代码示例 参数介绍 hist cv2.calcHist(images, channels, mask, histSize, ranges, hist, accumulate)-images&#xff1a;输入图像的列表。对于灰度图像&#xff0…

Prometheus安装

一、Prometheus的简介 Prometheus是一种开源的监控和警报工具&#xff0c;用于收集、存储和查询各种系统和服务的指标数据。它具有灵活的查询语言和强大的可视化功能&#xff0c;可用于实时监控应用程序性能和状态。 二、Prometheus下载 1、官网下载地址 下载Prometheus 2、P…

【regex】正则表达式

集合 [0-9.] [0-9.\-] 例子 正则表达式&#xff0c;按照规则写&#xff0c;写的时候应该不算困难&#xff0c;但是可读性差 不同语言中regex会有微小的差异 vim 需要转义&#xff0c; perl/python中不需要转义 锚位 \b am\b i am 命名 / 命名捕获组 ( 捕获组&#xff08;…

167基于matlab的根据《液体动静压轴承》编写的有回油槽径向静压轴承的程序

基于matlab的根据《液体动静压轴承》编写的有回油槽径向静压轴承的程序&#xff0c;可显示承载能力、压强、刚度及温升等图谱.程序已调通&#xff0c;可直接运行。 167 显示承载能力、压强、刚度及温升 (xiaohongshu.com)https://www.xiaohongshu.com/explore/65d212b200000000…

numpy模块:从基础到高级的完整指南【第88篇—NumPy数组操作】

numpy模块&#xff1a;从基础到高级的完整指南 在Python的科学计算领域&#xff0c;NumPy模块是一个不可或缺的利器。它提供了丰富的数学函数和矩阵操作&#xff0c;使得数据处理、分析和科学计算变得更加高效。本文将带你初步了解NumPy模块&#xff0c;并通过实例代码深入解析…

2.17日学习打卡----初学Dubbo(二)

2.17日学习打卡 目录: 2.17日学习打卡一. Dubbo入门案例需求介绍配置开发环境dubbo-producerdubbo-consumer运行测试IDEA开启DashBoard面板 二. Dubbo高级特性序列化协议安全地址缓存超时时间与配置覆盖关系重试机制多版本负载均衡集群容错服务降级服务限流原理服务限流实现结果…