Angular 18+ 高级教程 – Component 组件 の Template Binding Syntax
前言
这篇介绍一些基本的 Angular 模板语法。
参考
Render、Event Listening and DOM Manipulation
Angular 作为一个 MVVM 框架,有两个任务是一定要处理好的
1. First Render
2. Event Listening & DOM Manipulation
Render Engine
First Render 的工作是把 data 和 template 做 binding and render, 这世上有太多种模板语法了。
EJS, mustache, handlebars, JSX, Liquid, Razor


只能说各花入各眼吧。Angular 也整了一套自己独一无二的语法。不像 JSX 和 Razor 那样直接把 JS / C# 结合 HTML。
Angular 的思想是尽可能不去破坏 Native HTML 的规则,尽可能少的去加入新语法(虽说尽可能少, 但其实并不少...)
Event Listening & DOM Manipulation
除了 first render,用户交互也是避不开的,监听事件、修改 DOM 也是一个 MVVM 框架的任务。
常用的 Template Binding Syntax
我先介绍一些简单和常用的。先大概过一遍, 体会一下。下一章节我们再 focus 一些比较复杂的。
常见的 DOM Manipulation 有
const element = document.querySelector<HTMLElement>('.selector')!; // query element
element.textContent = 'value'; // update text
element.title = 'title'; // update property
element.setAttribute('data-value', 'value'); // set attribute (note: attribute and property are not the same thing)
element.style.padding = '16px'; // change style
element.classList.add('new-class'); // add class
const headline = document.createElement('h1'); // create element
headline.textContent = 'Hello World';
element.appendChild(headline); // append a element
element.innerHTML = `<h1>Hello World</h1>`; // write raw HTML
element.addEventListener('click', () => console.log('clicked')); // listen and handle a event
Text Binding
<h1>{{ value }}</h1>
value 来自组件的 property
Property Binding
<h1 [title]="value">Hello World</h1>
<h1 [title]="'Hello World'">Hello World</h1>
<h1 [title]="(100 - 10 === 90) ? 'Hello World' : 'Hello Kitty'">Hello World</h1>
当左边是 [放括弧],右边的输入就变成了 JavaScript expression(不是所有 expression 都支持)
第一行 value 是 binding with component property
第二行是 hardcode 一个 string。注意它有 quote,因为是 JavaScript expression。
第三行是一个复杂的 expression(注: 虽然 Angular 允许写 expression,但请尽量不要把逻辑放到 HTML,这会让 HTML 看上去很乱)
Attribute Binding
<h1 [attr.data-value]="value">Hello World</h1>
和 property 写法一样,只是前面加了 prefix attr。
attribute 和 property 是不同的东西哦,如果你不知道,请看这篇 Attribute 和 Property 的区别.
Style Binding
<!-- single style + declare unit in property side -->
<h1 [style.padding.px]="value">Hello World</h1> <!-- declare unit in value side -->
<h1 [style.padding]="'16px'">Hello World</h1> <!-- multiple style property declare -->
<h1 [style.padding.px]="16" [style.width]="'100px'">Hello World</h1> <!-- multiple style by passing object -->
<h1 [style]="{ padding : '16px', width: '100px' }"></h1> <!-- Error: declare unit in property side won't work when using passing object mode -->
<h1 [style]="{ 'padding.px' : 16, width: '100px' }"></h1> <!-- use value null or undefined to remove the specify style -->
<h1 [style]="{ padding : null, width: '100px' }"></h1>
上面给的例子很多是 hardcode,这个是为了我方便写,全部都可以改成 link with component property 的。
style 写法蛮多的,但常用的是第一种而已。
[style]="object" 这个写法不好, 它不支持 .px suffix 而且修改 property 是不会 re-render 的, 只有给予整个全新的 object 才会 re-render(简单说, 就是需要 immutable object)
所以 best practice 是用 ngStyle 指令来做 multiple style(这个以后会教)。
Class Binding
<!-- if value true, then class will be added -->
<h1 [class.my-class]="true">Hello World</h1> <!-- use object for multiple -->
<h1 [class]="{ 'my-class' : true, 'second-class': true }">Hello World</h1> <!-- use array -->
<!-- note: value should not be null or undefined, empty string is ok -->
<h1 [class]="['my-class', 'second-class']">Hello World</h1> <!-- use string -->
<h1 [class]="'my-class second-class'">Hello World</h1>
和 style binding 一样,[class]="object | array" 同样要求 immutable for re-render
for multiple class,best practice 是使用 ngClass 指令(这个以后会教)。
Event Listening
<button (click)="doSomething($event)">Click Me</button>
<input (keydown)="doSomething($event)">
<!-- 还可以指定只监听某个 key 哦 -->
<input (keydown.space)="doSomething($event)">
<input (keydown.enter)="doSomething($event)">
<input (keydown.shift.a)="doSomething($event)"> <!-- conditional execute -->
<input (keydown)="(100 - 10 === 90) ? doSomething($event) : doSomethingElse($event)">
<input (keydown)="(100 - 10 === 90) && doSomething($event)">
监听事件用的 (元括弧),右边依然是 JavaScript expression
doSomething 是 component method,$event 是一个特殊关键字,它代表了这个事件监听 emit / dispatch 的 event。
for click 的话是 MouseEvent,keydown 则是 KeyboardEvent
export class AppComponent {
doSomething(event: MouseEvent | KeyboardEvent) {
console.log(event);
}
}
效果

preventDefault
handler 如果返回 false,Angular 会执行 preventDefault 方法。
<input (keydown)="doSomething()">
export class AppComponent {
doSomething() {
return false;
}
}
相关源码在 dom_renderer.ts 的 DefaultDomRenderer2.listen 方法

当然我们也可以直接 $event.preventDefault()。
Host Binding and Listening
<app-test (click)="handleClick()" [title]="'test only'"></app-test>
上面这样是在组件外的 html 上对组件做 binding 和 listening.
试想, 如果我们想把这些逻辑也封装到组件里. 我该如何在组件内对组件本身 binding 和 listening 呢?
你可能会想, 我们可以在组件内的 html wrap 一个 container element. 然后监听它. 但这样就太间接了,Angular 不喜欢这样 (Thinking in Angular Way)
component metadata host
在组件 metadata 里声明 binding 和 listening
@Component({
selector: 'app-test',
standalone: true,
imports: [CommonModule],
templateUrl: './test.component.html',
styleUrl: './test.component.scss',
// 这里声明对 host 的 binding 和 listening
host: {
'(click)': 'handleClick($event)',
'[title]': 'title',
},
})
export class TestComponent {
title = 'Hello World';
handleClick(event: MouseEvent) {
console.log(event);
}
}
HostBinding can't use as @Input
host binding 是用来设置 HTML attribute 的,比如上面的 title。
我们不可以把它用于组件的 @Input

上面这样是不正确的。
@HostBinding 和 @HostListener
另一种方法是通过 decorator
export class TestComponent {
@HostBinding('title')
title = 'Hello World';
@HostListener('click', ['$event'])
handleClick(event: MouseEvent) {
console.log(event);
}
}
2 个方案选其中一个用就可以了哦.
component metadata vs decorator
metadata 比较 readable
decorator 是 Angular 正在远离的方式,所以推荐用 metadata 就好。
Two-way Binding 双向绑定 [()]
看例子
App 组件有一个 value
export class AppComponent {
value = 'Hello World';
}
它被显示在 App,同时传入 Hello World 组件。
<p>outside value: {{ value }}</p>
<app-hello-world [value]="value"></app-hello-world>
<button (click)="value = 'new outside value'">change value from outside</button>
并且 App 有一个 button,点击后可以修改这个 App.value。
HelloWorld 组件 @Input 接收这个 value
export class HelloWorldComponent {
@Input({ required: true })
value!: string;
}
然后显示
<p>inside value : {{ value }}</p>
效果

当外部(App)更新值,内部(HelloWorld)也会更新到。
好,我们试试反过来,内部做更新,看看外部是否也会更新。
把 button 移到 HelloWorld 组件内
<p>inside value : {{ value }}</p>
<button (click)="value = 'new inside value'">change value from inside</button>
效果

外部 App 的值没有被更新。那如果我们希望它被更新,那就叫双向绑定。
怎么实现呢?答案是用 @Output
export class HelloWorldComponent {
@Input({ required: true })
value!: string;
@Output()
valueChange = new EventEmitter<string>();
}
定义一个 @Output valueChange EventEmitter
hello-world.component.html 点击后 emit
<button (click)="valueChange.emit('new value')">change value from inside</button>
app.component.html
监听 valueChange event 然后更新 value
<app-hello-world [value]="value" (valueChange)="value = $event"></app-hello-world>
效果

关键就在 HelloWorld 组件只负责监听修改 value 的事件,正真修改 value 是由 App 负责的。
这样双方读取的始终是同一个源。
Angular 做了一个语法糖给下面这句代码
[value]="value" (valueChange)="value = $event"
加糖后变成
[(value)]="value"
Signal-based Two-way Binding (a.k.a Signal Models)
Angular v16.0.0 发布了 Signals,请先确保你已经掌握 Signals 才继续看。
Angular v17.1.0 发布了 Signal-based Input,请先确保你已经掌握 Signal-based Input 才继续看。
Angular v17.2.0 发布了 Signal-based Two-way Binding,它便是用于双向绑定的。
同样上面的例子,我们把 HelloWorld 组件的 @Input 和 @Output 改成 Signal-based Two-way Binding。
export class HelloWorldComponent {
// before
// @Input({ required: true })
// value!: string;
// @Output()
// valueChange = new EventEmitter<string>();
// after
value = model.required<string>(); // 这就是 Signal-based Two-way Binding 写法,和 Signal-based Input 如出一辙
}
HelloWorld Template
<!-- before -->
<!--
<p>inside value : {{ value }}</p>
<button (click)="valueChange.emit('new value')">change value from inside</button>
--> <!-- after -->
<p>inside value : {{ value() }}</p>
<button (click)="value.set('new value')">change value from inside</button>
把 valueChange.emit 换成 value.set,把 {{ value }} 换成 {{ value() }}
App 组件
export class AppComponent {
// before
// value = 'Hello World';
// after
value = signal('Hello World');
}
一样使用 signal
App Template
<!-- before -->
<!-- <p>outside value: {{ value }}</p> -->
<!-- <app-hello-world [value]="value" (valueChange)="value = $event"></app-hello-world> --> <!-- after -->
<p>outside value: {{ value() }}</p>
<app-hello-world [(value)]="value"></app-hello-world>
把 {{ value }} 换成 {{ value() }},把 [value]="value" (valueChange)="value = $event" 换成 [(value)]="value" (注:这里没有括弧,binding 的是 Signal 对象而不是它的值)。
新旧搭配使用
上面给的是 all in Signal 的写法。其实也不一定要 all in,它是支持一半一半的,我们继续看例子
App 组件
export class AppComponent {
readonly person = { name: 'old value' }
}
person 是一个普通对象,不是 Signal。
<app-hello-world [(value)]="person.name" />
<p>outside value: {{ person.name }}</p>
当内部更新 value 时,外部一样会更新。
其原理是

compile 以后,内部更新值时会执行 appInstance.person.name = newValue 这句代码,所以外部就更新了。
另外,想拆开 binding 也是可以的。
<app-hello-world [value]="person.name" />
虽然里面是 model,但外部只使用了它 input 的功能,所以当内部更新时,外部不会更新。
再来
<app-hello-world [value]="person.name" (valueChange)="doSomethingWithNewValue($event)" />
当内部修改 value 时,person.name 不会更新,同时 valueChange 会被调用,我们可以做对新值做任何处理。
再来
<app-hello-world [(value)]="valueSignal" /> <app-hello-world [value]="valueSignal()" (valueChange)="valueSignal.set($event)" />
这两个写法是等价的

model 不支持 transform
相关 Github Issue – Add "transform" to model()
严格来讲,model !== input + output
它们还是有一点点区别的,比如说 model 不支持 transform,而 input 是支持 transform 的。
我们看个简单例子
Checkbox 组件
export class CheckboxComponent {
readonly checked = input(false, { transform: booleanAttribute });
readonly checkedChange = output<boolean>();
}
使用 input + output
Checkbox Template
<input id="my-checkbox-1" type="checkbox" #checkbox [checked]="checked()" (change)="checkedChange.emit(checkbox.checked)">
<label for="my-checkbox-1"><ng-content /></label>
App Template
<app-checkbox checked>check me</app-checkbox>
我们可以直接写一个 checked attribute 来表达 checked,因为内部会 transform string to boolean
效果

假如我们要 two-way bindding with Signal 可以这样写
App 组件
export class AppComponent {
readonly checked = signal(true);
}
App Template
<app-checkbox [(checked)]="checked">check me</app-checkbox>
<pre>{{ checked() }}</pre>
效果

好,现在我们把它从 input + output 改成 model
Checkbox 组件
export class CheckboxComponent {
// readonly checked = input(false, { transform: booleanAttribute });
// readonly checkedChange = output<boolean>();
readonly checked = model(false); // model 不支持 transform: booleanAttribute
}
Checkbox Template
<input id="my-checkbox-1" type="checkbox" #checkbox [checked]="checked()" (change)="checked.set(checkbox.checked)">
App Template
<app-checkbox [(checked)]="checked">check me</app-checkbox>
到这里都没有问题,但是由于 model 不支持 transform,所以下面这样写就不行了

我们只能老老实实传 boolean 类型进去了。
<app-checkbox [checked]="true">check me</app-checkbox>
Angular 之所以不让 model 支持 transform 是因为它们怕乱,如果使用 model,那动机就是以同步为主,而不是 input + output 这种分开的形式。
目录
上一篇 Angular 18+ 高级教程 – Component 组件 の Angular Component vs Shadow DOM (CSS Isolation & slot)
下一篇 Angular 18+ 高级教程 – Component 组件 の Attribute Directives 属性型指令
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular 18+ 高级教程 – Component 组件 の Template Binding Syntax的更多相关文章
- Vue教程:组件Component详解(六)
一.什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功 ...
- 微信小程序template模板与component组件的区别和使用
前言: 除了component,微信小程序中还有另一种组件化你的方式template模板,这两者之间的区别是,template主要是展示,方法则需要在调用的页面中定义.而component组件则有自己 ...
- vue 基础-->进阶 教程(3):组件嵌套、组件之间的通信、路由机制
前面的nodejs教程并没有停止更新,因为node项目需要用vue来实现界面部分,所以先插入一个vue教程,以免不会的同学不能很好的完成项目. 本教程,将从零开始,教给大家vue的基础.高级操作.组件 ...
- 一篇文章看懂angularjs component组件
壹 ❀ 引 我在 angularjs 一篇文章看懂自定义指令directive 一文中详细介绍了directive基本用法与完整属性介绍.directive是个很神奇的存在,你可以不设置templa ...
- Angular CLI 使用教程指南参考
Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装 要安装Ang ...
- angular里使用vue/vue组件怎么在angular里用
欢迎加入前端交流群交流知识&&获取视频资料:749539640 如何在angularjs(1)中使用vue参考: https://medium.com/@graphicbeacon/h ...
- angularjs中directive指令与component组件有什么区别?
壹 ❀ 引 我在前面花了两篇博客分别系统化介绍了angularjs中的directive指令与component组件,当然directive也能实现组件这点毋庸置疑.在了解完两者后,即便我们知道co ...
- vue高级进阶( 三 ) 组件高级用法及最佳实践
vue高级进阶( 三 ) 组件高级用法及最佳实践 世界上有太多孤独的人害怕先踏出第一步. ---绿皮书 书接上回,上篇介绍了vue组件通信比较有代表性的几种方法,本篇主要讲述一下组件的高级用法和最 ...
- 分享25个新鲜出炉的 Photoshop 高级教程
网络上众多优秀的 Photoshop 实例教程是提高 Photoshop 技能的最佳学习途径.今天,我向大家分享25个新鲜出炉的 Photoshop 高级教程,提高你的设计技巧,制作时尚的图片效果.这 ...
- avalon2学习教程13组件使用
avalon2最引以为豪的东西是,终于有一套强大的类Web Component的组件系统.这个组件系统媲美于React的JSX,并且能更好地控制子组件的传参. avalon自诞生以来,就一直探索如何优 ...
随机推荐
- Python爬虫(1-4)-基本概念、六个读取方法、下载(源代码、图片、视频 )、user-agent反爬
Python爬虫 一.爬虫相关概念介绍 1.什么是互联网爬虫 如果我们把互联网比作一张大的蜘蛛网,那一台计算机上的数据便是蜘蛛网上的一个猎物,而爬虫程序就是一只小蜘蛛,沿着蜘蛛网抓取自己想要的数据 解 ...
- 使用 useLazyFetch 进行异步数据获取
title: 使用 useLazyFetch 进行异步数据获取 date: 2024/7/20 updated: 2024/7/20 author: cmdragon excerpt: 摘要:&quo ...
- Kubernetes 存储概念之Volumes介绍
Volumes 默认情况下容器中的磁盘文件是非持久化的,对于运行在容器中的应用来说面临两个问题,第一:当容器挂掉,K8S重启它时,文件将会丢失:第二:当Pod中同时运行多个容器,容器之间需要共享文件时 ...
- Django model层之执行原始SQL查询
Django model层之执行原始SQL查询 by:授客 QQ:1033553122 测试环境 Win7 Django 1.11 执行原始SQL查询 Manager.raw(raw_query, ...
- Jmeter函数助手23-intSum
intSum函数可用于计算两个或多个整数值的和. 要添加的第一个整数:必填,填入整数,不能为小数 要添加的第二个整数:必填,填入整数,不能为小数 存储结果的变量名(可选) 1. intSum函数求多个 ...
- 7月22号python 每日一题
7月22号python 每日一题 LCR 121. 寻找目标值 - 二维数组 难度:中等 m*n 的二维数组 plants 记录了园林景观的植物排布情况,具有以下特性: 每行中,每棵植物的右侧相邻植物 ...
- 【Java】JNDI实现
前提情要: 培训的时候都没讲过,也是书上看到过这么个东西,进公司干外包的第二个项目就用了这个鬼东西 自学也不是没尝试过实现,结果各种失败,还找不到问题所在,索性不写了 JNDI实现参考: 目前参考这篇 ...
- 【Docker】09 部署挂载本地目录的Redis
1.拉取Redis镜像: docker pull redis:6.0.6 2.执行挂载命令: docker run -d \ --name=redis \ --restart=always \ --p ...
- 阿里2021年春季实习笔试题(最后一道大题)(2020 China Collegiate Programming Contest, Weihai Site) (C. Rencontre codeforces.com/gym/102798)
实验室的慕师弟phd马上要毕业了,正准备先找个实习,投了阿里2021年春季实习的招聘,遇到最后一道编程大题没有思路事后找到了该题的最原始出处,即 2020 China Collegiate Progr ...
- 【转载】 IEEE Signal Processing Letters(SPL)投稿经验分享
版权声明:本文为CSDN博主「yellow7-」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/weixin_4 ...