Angular 核心概念
Angular 19 以 Standalone 组件为默认架构(不再需要 NgModule),并将 Signals 作为首选的响应式原语,取代 Zone.js 驱动的变更检测。本文覆盖现代 Angular 的核心概念。
Standalone 组件
Angular 17+ 默认创建 Standalone 组件,无需在 NgModule 中声明,直接通过 imports 声明依赖。
// app.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true, // 标记为独立组件
imports: [CommonModule, RouterOutlet], // 直接导入所需模块/组件
template: `
<h1>{{ title }}</h1>
<router-outlet />
`,
})
export class AppComponent {
title = 'my-app';
}// main.ts — 直接引导 Standalone 组件
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig);Signals — 响应式状态
Angular 16+ 引入 Signals,Angular 19 中已稳定。Signals 让状态变更可追踪,无需 Zone.js 即可精确触发更新。
基本用法
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>计数:{{ count() }}</p>
<p>双倍:{{ doubleCount() }}</p>
<button (click)="increment()">+1</button>
<button (click)="reset()">重置</button>
`,
})
export class CounterComponent {
// signal:创建响应式值
count = signal(0);
// computed:从其他 signal 派生(类似 Vue 的 computed)
doubleCount = computed(() => this.count() * 2);
constructor() {
// effect:自动追踪依赖,副作用(类似 Vue 的 watchEffect)
effect(() => {
console.log(`count 变为:${this.count()}`);
});
}
increment() {
this.count.update(v => v + 1); // 基于旧值更新
}
reset() {
this.count.set(0); // 直接设置新值
}
}Signal 操作方法
const name = signal('Alice');
// 读取
name() // "Alice"(调用函数读取)
// 修改
name.set('Bob') // 直接设置
name.update(v => v + '!') // 基于旧值更新
// 只读视图(暴露给外部,防止外部修改)
const readonlyName = name.asReadonly()在模板中使用 Signals
@Component({
template: `
@if (user()) {
<p>欢迎,{{ user()!.name }}</p>
} @else {
<p>请登录</p>
}
@for (item of items(); track item.id) {
<li>{{ item.name }}</li>
}
`,
})
export class ProfileComponent {
user = signal<User | null>(null);
items = signal<Item[]>([]);
}Angular 17+ 引入了新的控制流语法(
@if、@for、@switch),取代旧的 *ngIf、*ngFor 结构指令,性能更好,无需额外导入 CommonModule。模板语法
数据绑定
<!-- 属性绑定:将表达式绑定到 DOM 属性 -->
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isLoading">提交</button>
<!-- 事件绑定:处理 DOM 事件 -->
<button (click)="handleClick()">点击</button>
<input (input)="onInput($event)" (keydown.enter)="onEnter()">
<!-- 双向绑定(需要 FormsModule) -->
<input [(ngModel)]="username">
<!-- 文本插值 -->
<p>{{ user.name }}</p>
<!-- Class 与 Style 绑定 -->
<div [class.active]="isActive" [class.disabled]="isDisabled">...</div>
<div [ngClass]="{ active: isActive, dark: isDarkMode }">...</div>
<p [style.color]="textColor" [style.fontSize.px]="fontSize">...</p>控制流
<!-- @if / @else-if / @else -->
@if (status === 'loading') {
<app-spinner />
} @else if (status === 'error') {
<p class="error">{{ errorMsg }}</p>
} @else {
<app-content [data]="data" />
}
<!-- @for(必须指定 track) -->
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
} @empty {
<li>暂无数据</li>
}
<!-- @switch -->
@switch (role) {
@case ('admin') { <app-admin-panel /> }
@case ('user') { <app-user-panel /> }
@default { <p>未知角色</p> }
}组件输入/输出
import { Component, input, output, model } from '@angular/core';
@Component({
selector: 'app-input-demo',
standalone: true,
template: `
<input [value]="value()" (input)="value.set($event.target.value)">
<button (click)="submitted.emit(value())">提交</button>
`,
})
export class InputDemoComponent {
// input():Signal 化的输入(Angular 17.1+)
label = input<string>(''); // 可选输入,默认 ''
required = input.required<string>() // 必填输入
// model():双向绑定 input(等效于 value + valueChange)
value = model<string>('');
// output():事件发射
submitted = output<string>();
}<!-- 父组件使用 -->
<app-input-demo
label="用户名"
[(value)]="username"
(submitted)="onSubmit($event)"
/>服务与依赖注入
// services/user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { signal } from '@angular/core';
@Injectable({
providedIn: 'root' // 全局单例,自动注册
})
export class UserService {
private http = inject(HttpClient); // inject() 函数(推荐代替构造函数注入)
private _users = signal<User[]>([]);
// 只暴露只读 signal
readonly users = this._users.asReadonly();
loadUsers() {
this.http.get<User[]>('/api/users').subscribe(data => {
this._users.set(data);
});
}
getUserById(id: number) {
return this.http.get<User>(`/api/users/${id}`);
}
}// 在组件中使用服务
@Component({ ... })
export class UserListComponent {
private userService = inject(UserService);
// 直接用服务的 signal
users = this.userService.users;
ngOnInit() {
this.userService.loadUsers();
}
}管道(Pipe)
管道用于在模板中转换数据,不修改原始值:
{{ birthday | date:'yyyy-MM-dd' }} <!-- 日期格式化 -->
{{ price | currency:'CNY':'symbol' }} <!-- 货币格式化 -->
{{ title | uppercase }} <!-- 转大写 -->
{{ items | slice:0:5 }} <!-- 截取数组 -->
{{ data | json }} <!-- JSON 调试 -->
{{ text | async }} <!-- 订阅 Observable/Promise -->自定义管道:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'truncate', standalone: true })
export class TruncatePipe implements PipeTransform {
transform(value: string, limit = 50): string {
return value.length > limit ? value.slice(0, limit) + '…' : value;
}
}{{ article.content | truncate:100 }}最后更新于