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

[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,
    });
  }
}

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


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

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

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

相关文章

5G NR 频率计算

5G中引入了频率栅格的概念&#xff0c;也就是小区中心频点和SSB的频域位置不能随意配置&#xff0c;必须满足一定规律&#xff0c;主要目的是为了UE能快速的搜索小区&#xff1b;其中三个最重要的概念是Channel raster 、synchronization raster和pointA。 1、Channel raster …

【从Python基础到深度学习】1. 安装Python PyCharm

前言&#xff1a; 为了帮助大家快速入门机器学习-深度学习&#xff0c;从今天起我将用100天的时间将大学本科期间的所学所想分享给大家&#xff0c;和大家共同进步。【从Python基础到深度学习】系列博客中我将从python基础开始通过知识和代码实践结合的方式进行知识的分享和记…

.NET Core Web API使用HttpClient提交文件的二进制流(multipart/form-data内容类型)

需求背景&#xff1a; 在需要通过服务端请求传递文件二进制文件流数据到相关的服务端保存时&#xff0c;如对接第三方接口很多情况下都会提供一个上传文件的接口&#xff0c;但是当你直接通过前端Ajax的方式将文件流上传到对方提供的接口的时候往往都会存在跨域的情况&#xff…

详解各种LLM系列|LLaMA 1 模型架构、预训练、部署优化特点总结

作者 | Sunnyyyyy 整理 | NewBeeNLP https://zhuanlan.zhihu.com/p/668698204 后台留言『交流』&#xff0c;加入 NewBee讨论组 LLaMA 是Meta在2023年2月发布的一系列从 7B到 65B 参数的基础语言模型。LLaMA作为第一个向学术界开源的模型&#xff0c;在大模型爆发的时代具有标…

网络分析仪的防护技巧

VNA的一些使用防护技巧&#xff0c;虽不全面&#xff0c;但非常实用&#xff1a; [1] 一定要使用正规接地的三相交流电源线缆进行供电&#xff0c;地线不可悬浮&#xff0c;并且&#xff0c;火线和零线不可反接&#xff1b; [2] 交流供电必须稳定&#xff0c;如220V供电&#x…

如何使用websocket

如何使用websocket 之前看到过一个面试题&#xff1a;吃饭点餐的小程序里&#xff0c;同一桌的用户点餐菜单如何做到的实时同步&#xff1f; 答案就是&#xff1a;使用websocket使数据变动时服务端实时推送消息给其他用户。 最近在我们自己的项目中我也遇到了类似问题&#xf…

网络层DoS

网络层是OSI参考模型中的第三层&#xff0c;介于传输层和数据链路层之间&#xff0c;其目的 是实现两个终端系统之间数据的透明传送&#xff0c;具体功能包括&#xff1a;寻址和路由选择、连 接的建立、保持和终止等。位于网络层的协议包括ARP 、IP和ICMP等。下面就 ICMP为例&…

Android13新特性之预测返回手势

Android14新特性之预测返回手势 简介 Android 14引入了对预测性返回手势的支持&#xff0c;这意味着开发者可以通过系统提供的额外动画和API来实现定制化的动画效果。这一更新使得应用程序可以在用户执行返回手势时展示一个动画预览&#xff0c;例如在应用程序前显示Home屏幕…

FRP内网穿透如何避免SSH暴力破解(二)——指定地区允许访问

背景 上篇文章说到&#xff0c;出现了试图反复通过FRP的隧道&#xff0c;建立外网端口到内网服务器TCP链路的机器人&#xff0c;同时试图暴力破解ssh。这些连接造成了流量的浪费和不必要的通信开销。考虑到服务器使用者主要分布在A、B、C地区和国家&#xff0c;我打算对上一篇…

cleanmymacX和腾讯柠檬哪个好用

很多小伙伴在使用Mac时&#xff0c;会遇到硬盘空间不足的情况。遇到这种情况&#xff0c;我们能做的就是清理掉一些不需要的软件或者一些占用磁盘空间较大的文件来腾出空间。我们可以借助一些专门的清理工具&#xff0c;本文中我们来推荐几款好用的Mac知名的清理软件。并且将Cl…

Page 257~258 11.2.6处理“鼠标移动”消息

鼠标在当前窗口上移动&#xff0c;哪怕不单机&#xff0c;也会造成操作系统向该窗口发送消息。鼠标移动消息是WM_MOUSEMOVE,将它加入窗口过程函数的switch-case中&#xff1a; 我们自行编写的回调函数如下&#xff1a; 之前编写的OnPaint()函数也需要修改一下&#xff1a; 几点…

清华系2B模型杀出,性能吊打LLaMA-13B

2 月 1 日&#xff0c;面壁智能与清华大学自然语言处理实验室共同开源了系列端侧语言大模型 MiniCPM&#xff0c;主体语言模型 MiniCPM-2B 仅有 24 亿&#xff08;2.4B&#xff09;的非词嵌入参数量。 在综合性榜单上与 Mistral-7B 相近&#xff0c;在中文、数学、代码能力表现…

c++多态(2)-- 虚函数

我们在多态(1)中说到&#xff0c;多态就是使用父类指针访问子类函数&#xff0c;可以使得代码更加的简便。并且举了一个喂食动物的例子加以说明&#xff0c;我们使用代码进行展示。 enum class _ANIMALS_TYPE {CAT,DOG,ANIMAL_COUNT };class Animal { public:Animal(_ANIMALS_…

2024.2.6

1.现有无序序列数组为23,24,12,5,33,5347&#xff0c;请使用以下排序实现编程 函数1:请使用冒泡排序实现升序排序 函数2:请使用简单选择排序实现升序排序 函数3:请使用快速排序实现升序排序 函数4:请使用插入排序实现升序排序 #include<stdio.h> #include<string.h&g…

Linux操作系统基础(一):操作系统概述

文章目录 操作系统概述 一、计算机分类 二、计算机组成 三、操作系统概述 四、操作系统分类 操作系统概述 一、计算机分类 计算机一般分为个人计算机&#xff08;笔记、台式机&#xff09;与 企业级服务器&#xff08;1U、2U、机柜、塔式、刀片&#xff09;两种形式。 二…

日本的便宜服务器有哪些?

年底之际&#xff0c;无非是云服务器优惠的黄金时期&#xff0c;对于个人用户和独立开发者来说&#xff0c;无论你是搭建个人网站还是个人博客&#xff0c;现在都是行动的好时机。那么&#xff0c;对于这时要入手日本服务器的用户&#xff0c;该怎么找便宜厂商呢&#xff1f;这…

Shell脚本系列| SSH分发公钥方法 - expect脚本的使用

ssh原理&#xff1a;在SSH安全协议的原理中&#xff0c; 是一种非对称加密与对称加密算法的结合。用于确保远程登录和其他网络服务的会话安全&#xff0c;通过非对称加密、会话加密、多重验证机制等手段&#xff0c;保护数据传输的机密性和完整性。 ssh登录有2种方法&#xff1…

P2957

题目描述 The cows enjoy mooing at the barn because their moos echo back, although sometimes not completely. Bessie, ever the excellent secretary, has been recording the exact wording of the moo as it goes out and returns. She is curious as to just how mu…

Qt Windows和Android使用MuPDF预览PDF文件

文章目录 1. Windows MuPDF编译2. Android MuPDF编译3. 引用 MuPDF 库4. 解析本地PDF文件 1. Windows MuPDF编译 使用如下命令将MuPDF的源码克隆到本地 git clone --recursive git://git.ghostscript.com/mupdf.git直接用VS&#xff0c;打开 mupdf/platform/win32/mupdf.sln …

基于Skywalking开发分布式监控(二)

续上篇&#xff0c;上一篇主要是讲了为啥选skywalking&#xff0c;以及怎么有针对性改造SW Agent&#xff0c;现在我们继续看看如何构建自定义Trace跟踪链 要对SW Agent插件做适当剪裁&#xff0c;原来包括customize插件在内SW 8.9有100多个插件&#xff0c;如果没有作用也就罢…