Angular 快速入门:模板绑定与指令
Angular 快速入门:模板绑定与指令
上一篇文章我们搭好了项目、创建了组件,还渲染了一个静态的 Todo 列表。但现在的列表只能看,不能点、不能加、不能删——跟一张截图没什么区别。
这一篇我们来解决这个问题。你会学到 Angular 最核心的几个技能:
- 数据绑定:让数据和页面双向"通气"
- 事件绑定:点击、输入这些操作怎么触发逻辑
- 指令:
*ngIf、*ngFor、ngClass这些带ng前缀的魔法
学完之后,你的 Todo 应用从"能看"变成"能用"。
1. 插值 {{ }} — 把数据"塞"进模板
其实你已经用过了。在第一章我们写过:
<h2>{{ title }}</h2>
<li>{{ item.text }} - {{ item.done ? '已完成' : '未完成' }}</li>
{{ }} 就是"插值表达式",花括号里的内容会被 Angular 计算成字符串,然后替换到 HTML 里。
花括号里可以写任何有效的 TypeScript 表达式:
<p>{{ 1 + 1 }}</p> <!-- 2 -->
<p>{{ title.toUpperCase() }}</p> <!-- 调用方法 -->
<p>{{ todos.length > 0 ? '有数据' : '空的' }}</p>
对比一下别的框架:Vue 也是
{{ }},React 是单花括号{}(JSX 里)。用法几乎一样,区别只在 Angular 的模板里不能写太复杂的逻辑——Angular 的理念是"模板里只放表达式,复杂逻辑放到组件类里"。
2. 事件绑定 () — 点一下,干点啥
现在列表是静态的。我们希望点击一个待办项时,切换它的完成状态。
Angular 里监听事件用圆括号,语法是 (事件名)="处理函数()"。
先给 Todo 组件加一个切换方法。修改 todo.ts:
export class TodoComponent {
title = '我的待办清单';
todos = [
{ id: 1, text: '学习 Angular 基础', done: false },
{ id: 2, text: '写一个 Todo 应用', done: true },
{ id: 3, text: '对比 React 和 Vue 的差异', done: false }
];
// 新增:切换完成状态
toggleDone(id: number) {
const todo = this.todos.find(item => item.id === id);
if (todo) {
todo.done = !todo.done;
}
}
}
这里 title 和 todos 就是普通的 TypeScript 类属性。没有 useState,没有 ref(),没有 data()——就是直接赋值。
Angular 内置了一套变更检测机制,它自动追踪类属性的变化,一旦变了,模板里用到的地方就会自动更新。
对比一下其他框架:
- React:必须用
useState定义状态变量,调用setState才会触发更新。普通变量改了不会重新渲染。- Vue 3:Options API 用
data()返回对象,Composition API 用ref()或reactive()包裹,才能变成响应式。- Angular:类属性默认就是"可被检测"的。你直接
this.todos.push(...)或者this.title = '新标题'就行,Angular 会自动发现变化。听起来 Angular 更方便对吧?确实简单场景下是这样。但代价是 Angular 的变更检测机制更"重"——它会定期检查整个组件树,而不是像 React 那样精确到某个状态。应用大了之后,需要一些优化手段(比如
OnPush策略),这些我们后面再聊。
然后在模板 todo.html 里给每个 <li> 加上点击事件:
<ul>
<li *ngFor="let item of todos" (click)="toggleDone(item.id)">
{{ item.text }} - {{ item.done ? '已完成' : '未完成' }}
</li>
</ul>
保存,刷新浏览器。点击任意一个待办项,它的状态就会在"已完成"/"未完成"之间切换。
事件绑定的对比:
- React:
onClick={handleClick},直接在 JSX 的属性上写。- Vue:
@click="handleClick",@是v-on:的简写。- Angular:
(click)="toggleDone(item.id)",圆括号包裹事件名。调用时可以直接传参数,比如item.id,这一点跟 Vue 更像。三种框架都支持事件对象,Angular 里用
$event获取:<input (input)="onInput($event)">
3. 属性绑定 [] — 动态控制 HTML 属性
知道了怎么"点",但页面上看不出变化——"已完成"只是文字变了。我们想让它视觉上也不同,比如完成项加一条删除线。
这里需要把 item.done 的状态绑定到元素的样式上。
Angular 里绑定属性用方括号,语法是 [HTML属性名]="表达式"。
先给 <li> 动态设置一个 CSS 类:
<li *ngFor="let item of todos"
(click)="toggleDone(item.id)"
[class.done]="item.done">
{{ item.text }} - {{ item.done ? '已完成' : '未完成' }}
</li>
[class.done]="item.done" 的意思是:当 item.done 为 true 时,给这个元素加上 class done。
然后在 todo.css 里加上样式:
.done {
text-decoration: line-through;
color: #999;
}
现在点击待办项,完成的那条就会有一条删除线,颜色也变灰了。
属性绑定的对比:
- React:JSX 里
className={condition ? 'done' : ''},用 JS 表达式控制。- Vue:
:class="{ done: item.done }",:是v-bind:的简写。- Angular:
[class.done]="item.done",方括号是属性绑定的标志。Angular 这种写法优势是语义明确——看到
[class.xxx]就知道"这个 class 是动态的"。但写法上比 Vue 的:class对象语法稍微啰嗦一点。
除了 [class.xxx],还可以直接绑定样式:
<li [style.textDecoration]="item.done ? 'line-through' : 'none'">
或者绑定原生属性:
<img [src]="imageUrl">
<a [href]="linkUrl">去百度</a>
<button [disabled]="isLoading">提交</button>
方括号里放的是原生 HTML 属性名,等号右边是组件类里的表达式。
等等,这里有个很容易搞混的点:怎么区分等号右边是字符串还是变量?
关键就是看有没有方括号:
<!-- 没有方括号 → 纯字符串,照原样显示 --> <a href="linkUrl">去百度</a> <!-- 浏览器地址栏会显示 "linkUrl" 这个字符串本身 --> <!-- 有方括号 → 当做表达式求值 --> <a [href]="linkUrl">去百度</a> <!-- 会去组件类里找 linkUrl 变量,比如 'https://www.baidu.com' -->你可以把方括号理解成一个"开关"——有
[]就是表达式模式,没有就是字符串模式。这跟 Vue 的:(等同于v-bind:)是一个路数,React 则是因为 JSX 里{}无处不在,不存在这个区分。
4. 双向绑定 [(ngModel)] — 输入框和数据的"桥梁"
现在的列表数据是写死的。我们想加一个输入框,让用户自己添加待办。
这就需要"双向绑定"——用户在输入框里打字,组件里的数据跟着变;反过来,数据变了,输入框显示的内容也跟着变。
Angular 里双向绑定用 [(ngModel)],语法是 [(ngModel)]="属性名"。
不过要先做一件事:ngModel 不在 CommonModule 里,它来自 @angular/forms 的 FormsModule。需要先导入。
修改 todo.ts 的 imports:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; // ← 新增
@Component({
selector: 'app-todo',
standalone: true,
imports: [CommonModule, FormsModule], // ← 加上 FormsModule
templateUrl: './todo.html',
styleUrls: ['./todo.css']
})
export class TodoComponent {
// ...
}
然后在组件类里加一个 newTodoText 属性和添加方法:
export class TodoComponent {
title = '我的待办清单';
newTodoText = ''; // ← 新增:绑定输入框的值
todos = [
{ id: 1, text: '学习 Angular 基础', done: false },
{ id: 2, text: '写一个 Todo 应用', done: true },
{ id: 3, text: '对比 React 和 Vue 的差异', done: false }
];
toggleDone(id: number) {
const todo = this.todos.find(item => item.id === id);
if (todo) {
todo.done = !todo.done;
}
}
// 新增:添加待办
addTodo() {
if (!this.newTodoText.trim()) return;
this.todos.push({
id: Date.now(),
text: this.newTodoText,
done: false
});
this.newTodoText = ''; // 添加后清空输入框
}
}
最后在 todo.html 里加上输入框和添加按钮:
<h2>{{ title }}</h2>
<!-- 新增:输入区域 -->
<div>
<input [(ngModel)]="newTodoText" placeholder="输入新的待办...">
<button (click)="addTodo()">添加</button>
</div>
<ul>
<li *ngFor="let item of todos"
(click)="toggleDone(item.id)"
[class.done]="item.done">
{{ item.text }} - {{ item.done ? '已完成' : '未完成' }}
</li>
</ul>
刷新浏览器,你可以在输入框里打字,点"添加",新的待办就会出现在列表里。
双向绑定的对比:
- React:没有双向绑定。你需要
value={text}+onChange={e => setText(e.target.value)},两步手动实现。这叫"受控组件"。- Vue:
v-model="text",语法糖。- Angular:
[(ngModel)]="text",语法糖。其实 Angular 的
[(ngModel)]就是在背后帮你做了[value]="text"+(input)="text=$event"这两件事。方括号里再套圆括号——Angular 社区管这个叫**“香蕉盒子里装了一根香蕉”**([()]看起来像香蕉盒,里面是圆括号香蕉)。这个说法很 Angular——喜欢给东西起名字。不深究,记住
[()]是双向绑定的标志就行。
5. *ngIf — 条件显示
有时候列表是空的。空的列表展示起来很傻。用 *ngIf 来加一个"空状态"提示。
修改 todo.html:
<ul *ngIf="todos.length > 0; else emptyState">
<li *ngFor="let item of todos"
(click)="toggleDone(item.id)"
[class.done]="item.done">
{{ item.text }} - {{ item.done ? '已完成' : '未完成' }}
</li>
</ul>
<!-- 空状态模板 -->
<ng-template #emptyState>
<p>还没有待办,输入一条开始吧 🎉</p>
</ng-template>
现在删除所有待办,或者一开始列表就是空的,就显示"还没有待办…"的提示。
*ngIf 可以搭配 else,后面跟一个模板引用(#emptyState 这种写法叫"模板引用变量")。条件为真时显示 <ul>,为假时显示 <ng-template> 里定义的内容。
条件渲染的对比:
- React:
{todos.length > 0 ? <ul>...</ul> : <p>空的</p>},JS 三元表达式。- Vue:
<ul v-if="todos.length">...<p v-else>空的</p>。- Angular:
*ngIf="条件; else 模板名",else是个额外功能。三种框架都能实现条件渲染。React 最灵活(JS 想怎么写都行),Vue 和 Angular 的指令语法更声明式——看到
*ngIf就知道"这块是有条件显示的"。
*ngIf 还有几个常用变体:
<!-- 只显示第一个 true 的条件 -->
<div *ngIf="condition1; else ifBlock">条件1成立</div>
<ng-template #ifBlock><div *ngIf="condition2">条件2成立</div></ng-template>
<!-- 直接隐藏(元素还在 DOM 中) -->
<div [hidden]="!condition">跟 *ngIf 很像,但这个只是添加 HTML hidden 属性</div>
[hidden] 和 *ngIf 的区别:*ngIf 是从 DOM 里移除/添加元素,[hidden] 只是添加了 HTML 原生的 hidden 属性。如果你的元素创建/销毁成本高(比如有大量的子组件),用 [hidden] 性能更好。
6. *ngFor 深入 — 不只是循环
*ngFor 我们已经用过了,但它还有一些有用的"内置变量":
<li *ngFor="let item of todos; let i = index; let first = first; let last = last">
<span>{{ i + 1 }}.</span> <!-- 显示序号 -->
<span>{{ item.text }}</span>
<span *ngIf="first">🔝 排在第一</span>
<span *ngIf="last">✅ 最后一个</span>
</li>
这里 index(当前索引)、first(是否是第一个)、last(是否是最后一个)都是 *ngFor 自带的。
这点上 Angular 和 Vue 比较像:
*ngFor="let item of items; let i = index"对比 Vue 的v-for="(item, index) in items"。React 没有指令,你需要自己items.map((item, index) => ...)。
插一句:为什么有些指令前面有
*,有些没有?你可能注意到了,
ngClass和ngStyle前面没有星号,而*ngIf、*ngFor有。这可不是随便写的。带
*的是结构型指令——它们会添加或移除 DOM 元素。*ngIf条件为假时直接把元素从 DOM 里删了,*ngFor根据数组长度创建或销毁元素。不带
*的是属性型指令——它们只改变已有元素的样式或行为,不增删 DOM。ngClass只是给元素加/减 class,ngClass只是改内联样式。怎么记呢?看到
*就想到"动结构"(加/删元素),没有*就是"改外观"(改属性/样式)。这个规律对所有 Angular 指令都适用。
7. ngClass 和 ngStyle — 更灵活的样式控制
之前我们用 [class.done]="item.done" 控制了一个单独的 class。如果样式条件复杂了,可以用 [ngClass] 一次控制多个 class(对象):
<li [ngClass]="{
'done': item.done,
'high-priority': item.priority === 'high',
'selected': selectedId === item.id
}">
或者传一个 class (数组):
<li [ngClass]="['todo-item', item.done ? 'done' : '']">
同理,[ngStyle] 可以一次控制多个内联样式(对象):
<li [ngStyle]="{
'text-decoration': item.done ? 'line-through' : 'none',
'color': item.done ? '#999' : '#333',
'font-weight': item.done ? 'normal' : 'bold'
}">
但注意:能用 class 解决的,就别用 style。class 更干净、性能更好、也更容易维护。
样式绑定的对比:
- React:
className+ 条件,或者style={{ color: ... }}行内对象。- Vue:
:class="{ done: item.done }",对象语法和数组语法。- Angular:
[ngClass]/[ngClass],也是对象和数组都支持。其实用法大同小异,换了框架也就是查一下语法怎么写的问题。
8.完整代码一览
把上面的代码拼在一起,todo.ts 现在的全貌:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-todo',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './todo.html',
styleUrls: ['./todo.css']
})
export class TodoComponent {
title = '我的待办清单';
newTodoText = '';
todos = [
{ id: 1, text: '学习 Angular 基础', done: false },
{ id: 2, text: '写一个 Todo 应用', done: true },
{ id: 3, text: '对比 React 和 Vue 的差异', done: false }
];
toggleDone(id: number) {
const todo = this.todos.find(item => item.id === id);
if (todo) {
todo.done = !todo.done;
}
}
addTodo() {
if (!this.newTodoText.trim()) return;
this.todos.push({
id: Date.now(),
text: this.newTodoText,
done: false
});
this.newTodoText = '';
}
}
todo.html:
<h2>{{ title }}</h2>
<div>
<input [(ngModel)]="newTodoText" placeholder="输入新的待办...">
<button (click)="addTodo()">添加</button>
</div>
<ul *ngIf="todos.length > 0; else emptyState">
<li *ngFor="let item of todos"
(click)="toggleDone(item.id)"
[class.done]="item.done">
{{ item.text }} - {{ item.done ? '已完成' : '未完成' }}
</li>
</ul>
<ng-template #emptyState>
<p>还没有待办,输入一条开始吧 🎉</p>
</ng-template>
todo.css:
.done {
text-decoration: line-through;
color: #999;
}
9.本章总结
这一篇我们学会了 Angular 最核心的"三板斧":
- 数据绑定:插值
{{ }}、属性绑定[]、事件绑定()、双向绑定[()] - 常用指令:
*ngIf条件渲染、*ngFor列表渲染、ngClass和ngStyle动态样式 - CommonModule 和 FormsModule:standalone 模式下需要显式导入才能用这些功能
你的 Todo 应用已经从一个"静态截图"变成了一个"可交互的工具"。
接下来可以继续深入:组件之间怎么传数据(@Input / @Output),以及怎么把数据逻辑抽到 Service 里——这两块就是下一篇的内容了。
更多推荐



所有评论(0)