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

news2025/1/19 2:40:54

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


之前的笔记:

  • [Angular 基础] - Angular 渲染过程 & 组件的创建

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

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

以上是能够实现渲染静态页面的基础


之前的内容主要学习了怎么通过绑定原生 HTML(style, class, click 等) 和 Angular(ngFor, (click), {{ string interpolation }} 等) 的事件和属性动态渲染静态页面,这里开始讲组件沟通之间的部分,让页面开始真正的动起来

也就是 组件(component)指令(directives) 的进阶学习

设置项目

目前项目的结构如下:

src/app/
├── app.component.css
├── app.component.html
├── app.component.ts
├── app.module.ts
├── cockpit
│   ├── cockpit.component.css
│   ├── cockpit.component.html
│   └── cockpit.component.ts
└── server-element
    ├── server-element.component.css
    ├── server-element.component.html
    └── server-element.component.ts

3 directories, 10 files

app

其中最基层的 app 的作用是存储一个 serverList,并且使用 serverList 去渲染对应的 cockpitserver-element,具体文件如下:

  • VM 层

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      serverElements = [];
    }
    
  • V 层

    <div class="container">
      <app-cockpit></app-cockpit>
      <hr />
      <div class="row">
        <div class="col-xs-12">
          <app-server-element
            *ngFor="let element of serverElements"
          ></app-server-element>
        </div>
      </div>
    </div>
    

    这里就会开始涉及组件之间的沟通:

    • cockpit 会创建一个 server,并且将数据添加到 serverElements
    • server-element 会接受 element,也就是 for 循环里的元素

cockpit

有些无关紧要的说明:

駕駛艙(英語:Cockpit),是飞行员控制飛機的座艙,通常位於一架飛機的前端。除了早期的部分飛機,如今大部分飛機的駕駛艙采用密閉式的設計。

这里命名为 cockpit 大概是因为一个 server 既可以是 server,也可以是一个 blueprint。这个不用细究 class/object 的区别,主要还是自定义事件和属性方面的问题

  • VM 层

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-cockpit',
      templateUrl: './cockpit.component.html',
      styleUrl: './cockpit.component.css',
    })
    export class CockpitComponent {
      newServerName = '';
      newServerContent = '';
    
      onAddServer() {
      }
    
      onAddBlueprint() {
    }
    
  • V 层

    <div class="row">
      <div class="col-xs-12">
        <p>Add new Servers or blueprints!</p>
        <label>Server Name</label>
        <input type="text" class="form-control" [(ngModel)]="newServerName" />
        <label>Server Content</label>
        <input type="text" class="form-control" [(ngModel)]="newServerContent" />
        <br />
        <div class="btn-toolbar">
          <button class="btn btn-primary" (click)="onAddServer()">
            Add Server
          </button>
          <button class="btn btn-primary" (click)="onAddBlueprint()">
            Add Server Blueprint
          </button>
        </div>
      </div>
    </div>
    

server-element

这里会接受一个 server,并且将其渲染到页面上

  • VM 层

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-server-element',
      templateUrl: './server-element.component.html',
      styleUrl: './server-element.component.css',
    })
    export class ServerElementComponent {}
    
  • V 层

    <div class="panel panel-default">
      <div class="panel-heading">{{ element.name }}</div>
      <div class="panel-body">
        <p>
          <strong *ngIf="element.type === 'server'" style="color: red"
            >{{ element.content }}</strong
          >
          <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
        </p>
      </div>
    </div>
    

此时因为组件之间的交流还没有完成,所以代码运行肯定会失败的,不过最基础的是已经完成了

绑定自定义属性

首先是从渲染 server-listserver-element 开始,所以需要将 cockpit 内的东西注释掉,以防报错

如果不会报错的话则可以忽略,我后面又做了点修改……

model

先新建一个 server-element 的 model 让其他文件引用,我改了下结构,现在 model 在这里:

❯ tree src/app/
src/app/
├── model
│   └── server-element.model.ts

内容如下:

export class ServerElement {
  constructor(
    public name: string,
    public type: 'server' | 'blueprint',
    public content: string
  ) {}
}

app VM 层

这里主要就是在数组里放一个数据,新增代码如下:

export class AppComponent {
  serverElements: ServerElement[] = [
    { type: 'server', name: 'Testserver', content: 'Just a test!' },
  ];
}

app V 层

这里会更新一下代码,绑定 自定义属性 element

<div class="container">
  <app-cockpit></app-cockpit>
  <hr />
  <div class="row">
    <div class="col-xs-12">
      <app-server-element
        *ngFor="let serverElement of serverElements"
        [element]="serverElement"
      ></app-server-element>
    </div>
  </div>
</div>

其中 [element]="serverElement" 就是新增的代码,也就是绑定的 自定义属性

server-element V 层

这里是选择接受参数的地方,已经从上面的 V 层知道传进来的自定义属性是 element,因此这里就用 element 作为变量名:

<div class="panel panel-default">
  <div class="panel-heading">{{ element.name }}</div>
  <div class="panel-body">
    <p>
      <strong *ngIf="element.type === 'server'" style="color: red"
        >{{ element.content }}</strong
      >
      <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
    </p>
  </div>
</div>

server-element VM 层

VM 层是掌管数据的地方,因此 VM 层还需要声明一下 element 的存在:

import { Component } from '@angular/core';
import { ServerElement } from '../model/server-element.model';

@Component({
  selector: 'app-server-element',
  templateUrl: './server-element.component.html',
  styleUrl: './server-element.component.css',
})
export class ServerElementComponent {
  // 不做类型声明也不会报错,但是会有简易
  element: ServerElement;
}

这时候效果如下:

在这里插入图片描述

Angular 渲染了一个元素,但是这个元素是空的,这个原因是因为 scoping 的问题,element 本质上还是只对父组件——即 app 组件——可见,如果想让它在子组件里也能被访问到,需要用一个新的装饰器:@Input(),修改如下:

export class ServerElementComponent {
  @Input() element: ServerElement;
}

随后即可正常渲染:

在这里插入图片描述

⚠️:Input 需要从 @angular/core 中导入

自定义属性的 alias

有的时候会想要设置 alias,而非使用传递过来的变量名——比如说可能父元素会创建一个事件然后传递 event 到子元素中,子元素则可以根据需求去重命名这是一个 mouseEvent, inputEvent, formEvent 或是其他,修改方法如下:

export class ServerElementComponent {
  // () 内的才是父组件里使用的变量名
  @Input('element') aliasElement: ServerElement;
}

这个时候,对于当前组件来说,可访问的变量为 aliasElement,因此 V 层也需要进行对应的修改:

<div class="panel panel-default">
  <div class="panel-heading">{{ aliasElement.name }}</div>
  <div class="panel-body">
    <p>
      <strong *ngIf="aliasElement.type === 'server'" style="color: red"
        >{{ aliasElement.content }}</strong
      >
      <em *ngIf="aliasElement.type === 'blueprint'"
        >{{ aliasElement.content }}</em
      >
    </p>
  </div>
</div>

绑定自定义事件

这个时候需要将 cockpit 里的代码还原

这里同样需要注意的一点是数据的传输方向,在父组件中,只有 serverElements 被声明了,具体的添加事件是发生在子组件中的,也就是说,事件的传输方向并不是由父组件向子组件进行传输,而是从子组件传递到父组件。准确的说也不是传送,而是发送(emit 🚀)。和 React 相反,Angular 的事件通常情况下是从子组件发送到父组件,父组件通过监听事件进行对应的处理

其实这个处理大方向和上面绑定自定义属性差不多,最大的差别就是 flow

cockpit VM 层

实现如下:

export class CockpitComponent {
  @Output() serverCreated = new EventEmitter<Omit<ServerElement, 'type'>>();
  @Output() blueprintCreated = new EventEmitter<Omit<ServerElement, 'type'>>();
  newServerName = '';
  newServerContent = '';

  onAddServer() {
    this.serverCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }

  onAddBlueprint() {
    this.blueprintCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }
}

⚠️:这里的 Output 同样需要从 angular-core 导入

👀:注意这里的语法,这是一个 EventEmitter,并且类型是 Output。这也说明了事件的方向是自下而上,而非自上而下——对比 React,React 将 event handler 从上往下传,并在子元素进行调用

cockpit V 层

保持不变

app VM 层

变动如下

export class AppComponent {
  serverElements: ServerElement[] = [
    { type: 'server', name: 'Testserver', content: 'Just a test!' },
  ];
  serverData: ServerElement;

  onServerAdded(serverData: Omit<ServerElement, 'type'>) {
    this.serverElements.push({
      type: 'server',
      name: serverData.name,
      content: serverData.content,
    });
  }

  onBlueprintAdded(blueprintData: Omit<ServerElement, 'type'>) {
    this.serverElements.push({
      type: 'blueprint',
      name: blueprintData.name,
      content: blueprintData.content,
    });
  }
}

⚠️:Omit 是 TypeScript 的语法,详细的使用方法可以查看官方文档:Utility Types

app V 层

变动如下:

<div class="container">
  <app-cockpit
    (serverCreated)="onServerAdded($event)"
    (blueprintCreated)="onBlueprintAdded($event)"
  ></app-cockpit>
  <hr />
  <div class="row">
    <div class="col-xs-12">
      <app-server-element
        *ngFor="let serverElement of serverElements"
        [element]="serverElement"
      ></app-server-element>
    </div>
  </div>
</div>

实现后效果如下:

在这里插入图片描述

自定义事件的 alias

这个和自定义属性的方式实现的也差不多:

import { Component, EventEmitter, Output } from '@angular/core';
import { ServerElement } from '../model/server-element.model';

@Component({
  selector: 'app-cockpit',
  templateUrl: './cockpit.component.html',
  styleUrl: './cockpit.component.css',
})
export class CockpitComponent {
  @Output('serverCreated') svCreated = new EventEmitter<
    Omit<ServerElement, 'type'>
  >();
  @Output('blueprintCreated') bpCreated = new EventEmitter<
    Omit<ServerElement, 'type'>
  >();
  newServerName = '';
  newServerContent = '';

  onAddServer() {
    this.svCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }

  onAddBlueprint() {
    this.bpCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }
}

同样是 () 内的代表外部的变量名,而声明的则是组件内部可用的名称


到这里就实现了数据和事件的跨组件交流

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

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

相关文章

C语言-3

定义指针 /*指针的概念:1.为了方便访问内存中的内容&#xff0c;给每一个内存单元&#xff0c;进行编号&#xff0c;那么我们称这个编号为地址&#xff0c;也就是指针。2.指针也是一种数据类型&#xff0c;指针变量有自己的内存&#xff0c;里面存储的是地址&#xff0c;也就是…

VMware17上安装centos7.9

一、下载安装包&#xff1a; 1、VMware安装 VMware 下载地址&#xff1a; https://www.vmware.com/cn/products/workstation-pro.html VMware下载后安装即可 安装教程可以参考VMware安装教程 2、CentOs7.9下载地址&#xff1a; http://mirrors.aliyun.com/centos/7.9.2009/iso…

HTML+CSS:全景轮播

效果演示 实现了一个简单的网页布局&#xff0c;其中包含了五个不同的盒子&#xff0c;每个盒子都有一个不同的背景图片&#xff0c;并且它们之间有一些间距。当鼠标悬停在某个盒子上时&#xff0c;它的背景图片会变暗&#xff0c;并且文字会变成白色。这些盒子和按钮都被放在一…

如何用Hexo搭建一个优雅的博客

引言 在数字化时代&#xff0c;拥有一个个人博客已经成为许多人展示自己技能、分享知识和与世界互动的重要方式。而在众多博客平台中&#xff0c;Hexo因其简洁、高效和易于定制的特点而备受青睐。本文将详细介绍如何从零开始搭建一个Hexo博客&#xff0c;让你的个人博客在互联…

Git版本与分支

目录 一、Git 二、配置SSH 1.什么是SSH Key 2.配置SSH Key 三、分支 1.为什么要使用分支 2.四个环境及特点 3.实践操作 1.创建分支 2.查看分支 3.切换分支 4.合并分支 5.删除分支 6.重命名分支 7.推送远程分支 8.拉取远程分支 9.克隆指定分支 四、版本 1.什…

Linux操作系统基础(三):虚拟机与Linux系统安装

文章目录 虚拟机与Linux系统安装 一、系统的安装方式 二、虚拟机概念 三、虚拟机的安装 四、Linux系统安装 1、解压人工智能虚拟机 2、找到解压目录中的node1.vmx 3、启动操作系统 虚拟机与Linux系统安装 一、系统的安装方式 Linux操作系统也有两种安装方式&#xf…

蓝桥杯每日一题------背包问题(一)

背包问题 阅读小提示&#xff1a;这篇文章稍微有点长&#xff0c;希望可以对背包问题进行系统详细的讲解&#xff0c;在看的过程中如果有任何疑问请在评论区里指出。因为篇幅过长也可以进行选择性阅读&#xff0c;读取自己想要的那一部分即可。 前言 背包问题可以看作动态规…

js手写Promise(上)

目录 构造函数resolve与reject状态改变状态改变后就无法再次改变 代码优化回调函数中抛出错误 thenonFulfilled和onRejected的调用时机异步then多个then 如果是不知道或者对Promise不熟悉的铁铁可以先看我这篇文章 Promise 构造函数 在最开始&#xff0c;我们先不去考虑Promi…

精简还是全能?如何在 Full 和 Lite 之间做出最佳选择!关于Configuration注解的Full模式与Lite模式(SpringBoot2)

&#x1f3c3;‍♂️ 微信公众号: 朕在debugger© 版权: 本文由【朕在debugger】原创、需要转载请联系博主&#x1f4d5; 如果文章对您有所帮助&#xff0c;欢迎关注、点赞、转发和订阅专栏&#xff01; 前言 关于 Configuration 注解&#xff0c;相信在座的各位 Javaer 都…

可达鸭二月月赛——基础赛第六场(周五)题解,这次四个题的题解都在这一篇文章内,满满干货,含有位运算的详细用法介绍。

姓名 王胤皓 T1 题解 T1 题面 T1 思路 样例输入就是骗人的&#xff0c;其实直接输出就可以了&#xff0c;输出 Hello 2024&#xff0c;注意&#xff0c;中间有一个空格&#xff01; T1 代码 #include<bits/stdc.h> using namespace std; #define ll long long int …

Swift 使用 Combine 管道和线程进行开发 从入门到精通八

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…

ANSI Escape Sequence 下落的方块

ANSI Escape Sequence 下落的方块 1. ANSI Escape 的用途 无意中发现 B站有人讲解&#xff0c; 完全基于终端实现俄罗斯方块。 基本想法是借助于 ANSI Escape Sequence 实现方方块的绘制、 下落动态效果等。对于只了解 ansi escape sequence 用于 log 的颜色打印的人来说&…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第10章 项目进度管理(四)

博主2023年11月通过了信息系统项目管理的考试&#xff0c;考试过程中发现考试的内容全部是教材中的内容&#xff0c;非常符合我学习的思路&#xff0c;因此博主想通过该平台把自己学习过程中的经验和教材博主认为重要的知识点分享给大家&#xff0c;希望更多的人能够通过考试&a…

【Java EE】----SpringBoot的日志文件

1.SpringBoot使用日志 先得到日志对象通过日志对象提供的方法进行打印 2.打印日志的信息 3.日志级别 作用&#xff1a; 可以筛选出重要的信息不同环境实现不同日志级别的需求 ⽇志的级别分为&#xff1a;&#xff08;1-6级别从低到高&#xff09; trace&#xff1a;微量&#…

高级数据结构与算法 | 布谷鸟过滤器(Cuckoo Filter):原理、实现、LSM Tree 优化

文章目录 Cuckoo Filter基本介绍布隆过滤器局限变体 布谷鸟哈希布谷鸟过滤器 实现数据结构优化项Victim Cache备用位置计算半排序桶 插入查找删除 应用场景&#xff1a;LSM 优化 Cuckoo Filter 基本介绍 如果对布隆过滤器不太了解&#xff0c;可以看看往期博客&#xff1a;海量…

CentOS 7安装Nodejs

说明&#xff1a;本文介绍如何在云服务器上CentOS 7操作系统上安装Nodejs。以及安装过程中遇到的问题。 下载压缩包&解压 首先&#xff0c;先去官网下载Linux版本的Node。 将下载下来的压缩包&#xff0c;上传到云服务器上&#xff0c;解压。配置环境变量。 &#xff08…

VScode为什么选择了Electron,而不是QT?

选择Electron而不是QT可能是基于以下几个原因&#xff1a; Web技术的普及和开发者生态系统&#xff1a;Web技术如HTML、CSS和JavaScript在开发者中非常普及&#xff0c;开发者生态系统庞大且活跃。使用Electron可以利用这些熟悉的Web技术和丰富的开发者社区资源。跨平台支持&am…

蓝桥杯(Web大学组)2022国赛真题:水果消消乐

思路&#xff1a; 记录点击次数&#xff0c;点击次数为1时&#xff0c;记录点击下标&#xff08;用于隐藏or消除&#xff09;、点击种类&#xff0c;点击次数为2时&#xff0c;判断该下标所对应种类与第一次是否相同 相同&#xff1a;两个都visibility:hidden &#xff08;占…

黄金交易策略(EA):三个仓位的设计是确保可以不停息做单

完整EA&#xff1a;Nerve Knife.ex4黄金交易策略_黄金趋势ea-CSDN博客

MacOS 查AirPods 电量技巧:可实现低电量提醒、自动弹窗

要怎么透过macOS 来查询AirPods 电量呢&#xff1f;当AirPods 和Mac 配对后&#xff0c;有的朋友想通过Mac来查询AirPods有多少电量&#xff0c;这个里有几个技巧&#xff0c;下面我们来介绍一下。 透过Mac 查AirPods 电量技巧 技巧1. 利用状态列上音量功能查询 如要使用此功能…