第一个 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 层添加点击事件
<a
  href="#"
  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">
    <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">
      <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">
            <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>
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">
      <a
        class="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);
  }
}

最后完成效果如下:

在这里插入图片描述


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

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

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

相关文章

板块一 Servlet编程:第四节 HttpServletResponse对象全解与重定向 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第四节 HttpServletResponse对象全解与重定向 一、什么是HttpServletResponse二、响应数据的常用方法三、响应乱码问题字符流乱码字节流乱码 四、重定向&#xff1a;sendRedirect请求转发和重定向的区别 在上一节中&#xff0c;我们系统的学习了…

react封装通用Modal弹窗组件

目录 1、【src/component/modal/hoc.js】 2、【src/component/modal/componentModal.js】 3、【src/page/projectView.js】 【说明】&#xff1a;后台管理的项目中会经常遇到弹窗&#xff0c;于是封装了一个简单的公共弹窗组件 这个公共组件不适用复杂的功能&#xff0c;简单的…

业务型 编辑器组件的封装(复制即可使用)

使用需要安装 wangeditor npm i --save wangeditor import React from react; import E from wangeditor; import ./index.lessclass EditorElem extends React.Component {constructor(props) {super(props);this.isChange false;this.state {}}componentDidMount() {con…

并发List、Set、ConcurrentHashMap底层原理

并发List、Set、ConcurrentHashMap底层原理 ArrayList: List特点&#xff1a;元素有放入顺序&#xff0c;元素可重复 存储结构&#xff1a;底层采用数组来实现 public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Clon…

后端开发怎么学?

后端开发怎么学&#xff1f; 后端开发可以简单地理解为与前端开发相对应的开发方向。前端开发主要负责构建用户界面、维护用户体验等方面的工作&#xff0c;而后端开发则主要负责处理数据、逻辑和算法等方面的工作。后端开发旨在为前端应用程序提供支持&#xff0c;以帮助实现可…

stm32学习笔记-STLINK使用

stm32学习笔记-STLINK使用 使用ST-LINK调试程序进度表格 使用ST-LINK调试程序 说明 组成 总结 记录使用STLINK进行项目的烧写和调试&#xff0c;旨在高效的进行代码调试学习工具包括笔记本、keil5MDK、stm32f030c8t6电表主机、STLINK V2、导线、电表代码总的来说&#xff0…

vue3项目配置按需自动引入自定义组件unplugin-vue-components

我们通常在项目中&#xff0c;需要手动引入自定义的各种组件&#xff0c;如果涉及的页面功能比较多的话&#xff0c;光是import的长度都能赶上春联了。 如果&#xff0c;能有一个插件帮我们实现自动引入&#xff0c;是不是要谢天谢地了呢&#xff1f; 接下来就进入我们的主角u…

Uniapp-开发小程序

文章目录 前言一、npm run xxx —— cross-env: Permission denied解决方法&#xff08;亲测有效&#xff09;其他解决方法&#xff1a; 二、macOS 微信开发者工具选择uniapp 用 vscode 开发 总结 前言 macOS下 uniapp 开发小程序。 一、npm run xxx —— cross-env: Permissi…

数据结构:动态内存分配+内存分区+宏+结构体

一、作业 1.定义一个学生结构体&#xff0c;包含结构体成员&#xff1a;身高&#xff0c;姓名&#xff0c;成绩&#xff1b;定义一个结构体数组有7个成员&#xff0c;要求终端输入结构体成员的值&#xff0c;根据学生成绩&#xff0c;进行冒泡排序。 #include <stdio.h>…

大数据技术之 Kafka

大数据技术之 Kafka 文章目录 大数据技术之 Kafka第 1 章 Kafka 概述1.1 定义1.2 消息队列1.2.1 传统消息队列的应用场景1.2.2 消息队列的两种模式 1.3 Kafka 基础架构 第 2 章 Kafka 快速入门2.1 安装部署2.1.1 集群规划2.1.2 集群部署2.1.3 集群启停脚本 2.2 Kafka 命令行操作…

【Go语言】Go语言中的变量和常量

Go语言中的变量和常量 1 变量 变量相当于是对一块数据存储空间的命名&#xff0c;程序可以通过定义一个变量来申请一块数据存储空间&#xff0c;之后可以通过引用变量名来使用这块存储空间。 Go 语言是强类型静态语言&#xff0c;所以变量的声明与赋值方式与 PHP/Python 等动…

基于java的眼镜店仓库管理系统

源码获取&#xff0c;加V&#xff1a;qq2056908377 摘要&#xff1a; 随着电子商务的兴起&#xff0c;越来越多的商家选择在线销售他们的产品。眼镜店作为零售业的一种&#xff0c;也不例外。随着市场需求的不断增加&#xff0c;眼镜店需要更加高效的管理他们的仓库和库存&…

代码随想录算法训练营DAY20 | 二叉树 (8)

一、LeetCode 701 二叉搜索树中的插入操作 题目链接&#xff1a; 701.二叉搜索树中的插入操作https://leetcode.cn/problems/insert-into-a-binary-search-tree/description/ 思路&#xff1a;见缝插针罢辽。 class Solution {public TreeNode insertIntoBST(TreeNode root, i…

Spring基础-IOC理解及自己创建类+第三方提供的类注入的方法

Spring全称为Spring Framework,是一款主流的JAVA EE 开原框架,主要功能有:IOC(控制反转,层间解耦)、AOP&#xff08;面向切面编程&#xff0c;公共代码抽取&#xff09;、MVC&#xff08;开发web应用程序&#xff09;、数据库事务管理、web单元测试&#xff08;与测试框架集成&…

【深入理解设计模式】单例设计模式

单例设计模式 概念&#xff1a; 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。 单例设计模式是一种创建型设计模式&#xff0c;其主要目的是确保类在应用程序中的一个实例只有一个。这意味着无论在应用程序的哪个位置请求该类的实例&a…

自定义异常处理演示

​ 为了防止黑客从前台异常信息&#xff0c;对系统进行攻击。同时&#xff0c;为了提高用户体验&#xff0c;我们都会都抛出的异常进行拦截处理。 一、全局异常处理 编写一个异常拦截类&#xff0c;如下&#xff1a;ControllerAdvice&#xff0c;很多初学者可能都没有听说过…

商品图放大镜效果实现

业务拆解 鼠标经过商品小图&#xff0c;展示块会显示对应商品图片鼠标经过展示块&#xff0c;右侧会显示放大镜效果的大图大图可跟随鼠标移动而显示对应的部分 关键JS代码 // 1. 获取三个盒子// 2. 小盒子 图片切换效果const small document.querySelector(.small)// 中盒子…

极狐GitLab 如何配置多个 LDAP?

本文仅适用于极狐GitLab私有化部署场景。 场景化痛点 极狐GitLab 的多 LDAP 接入功能解决了企业在以下场景中可能遇到的痛点&#xff1a; 多个组织/部门的整合&#xff1a;在大型企业或跨国公司中&#xff0c;往往存在多个组织或部门&#xff0c;它们可能拥有独立的 LDAP 服务…

las数据转pcd数据

las数据转pcd数据 一、算法原理1.介绍las2.主要函数 二、代码三、结果展示3.1 las数据3.2 las转换为pcd 四、相关数据链接 一、算法原理 1.介绍las LAS文件按每条扫描线排列方式存放数据,包括激光点的三维坐标、多次回波信息、强度信息、扫描角度、分类信息、飞行航带信息、飞…

LeetCode算法实践——前缀和从入门到入土

前缀和算法 对于一个数组a&#xff0c;和为s数组&#xff1b;其每一个下标的前缀和为s[0]0,s[i]s[i-1]a[i]。 从上面可以推导出left到right之间的前缀和为是s[right1]-s[left]。 例如a[3,2,1,2]&#xff0c;对应的前缀和数组为s[0,3,5,6,8]。a的子数组[2,1,2]的和就可以用s[…