0x01 概述
(1)简介
- 官方网站:https://www.angular.cn/
- Angular 由 Google 的专业团队维护,Angular 提供了广泛的工具、API 和库,简化和优化开发工作流程
- Angular 提供了一个坚实的平台,可用于构建快速、可靠、能够随着团队规模和代码库规模扩展的应用程序
- 特点:
- 整合性高,降低技术决策成本
- 简化 DOM 操作,注重业务逻辑
- 采用后端的依赖注入系统
- 与 React、Vue 对比,Angular:
- 在大型企业级应用中提供更成熟的框架和严格的架构模式,适合复杂项目的长期维护
- 初始加载时间较长,不够灵活和轻量
- 学习曲线较陡峭
(2)创建项目
Angular 18 于 2024 年 5 月正式发布,以下内容均采用 18.2.10 / 18.2.9 版本,NodeJS 采用 23.0.0 版本,npm 采用 10.9.0
- 使用命令
npm install -g @angular/cli 安装脚手架
- 使用命令
ng new angular-app 创建名为 angular-app 的项目
- 不向 Google 发送数据
- 不使用 CSS 预编译
- 不使用 SSR
- 使用命令
cd angular-app 进入项目目录
- 使用命令
ng serve --open 启动项目并自动开启浏览器
使用 ng 命令创建组件等:
| 脚手架 |
说明 |
命令 |
| Component |
组件 |
ng generate component my-new-component |
| Directive |
指令 |
ng generate directive my-new-directive |
| Pipe |
管道 |
ng generate pipe my-new-pipe |
| Service |
服务 |
ng generate service my-new-service |
| Class |
类 |
ng generate class my-new-class |
| Interface |
接口 |
ng generate interface my-new-interface |
| Enum |
枚举 |
ng generate enum my-new-enum |
| Module |
模块 |
ng generate module my-new-module |
(3)项目结构
-
.angular:编译缓存目录
-
node_modules:Node 包目录
-
public:公共资源目录
-
src:代码资源目录
-
app:App 模块代码目录
- app.component.css:组件样式文件
- app.component.html:组件模板文件
- app.component.spec.ts:组件测试文件
- app.component.ts:组件入口文件
- app.config.ts:模块配置文件
- app.routes.ts:模块路由文件
-
index.html:模板文件
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AngularApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
-
main.ts:入口文件
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
-
style.css:全局样式文件
-
.editorconfig:编辑器配置文件,详情参考 https://editorconfig.org
-
.gitignore:git 忽略文件
-
angular.json:Angular 配置文件
-
package.json:Node 包配置文件
-
在 "script" 项中添加以下内容:
{
"scripts": {
"dev": "ng serve --open",
// ...
},
}
以后通过命令 npm run dev 来启动项目(代替命令 ng serve --open)
-
tsconfig.json:TypeScript 配置文件
flowchart TB
项目-->模块1 & 模块2 & m[...] & 模块n
模块1-->组件1 & 组件2 & c[...] & 组件n
0x02 组件
(1)简介
以 app.component.ts 为例:
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'angular-app';
}
(2)创建组件
-
使用命令 ng generate component hello(或 ng g c hello)创建一个在 app 目录下,名为 hello 的新组件
-
app 目录结构
flowchart TB
app-->hello & app.component.css & app.component.html & ...
hello-->hello.component.css & hello.component.html & hello.component.spec.ts & hello.component.ts
-
修改 hello.component.html
<div style="width: 200px; height: 200px; background-color: red">
这里是 hello 组件
</div>
-
修改 hello.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
standalone: true,
imports: [],
templateUrl: './hello.component.html',
styleUrl: './hello.component.css'
})
export class HelloComponent {
}
-
修改 app.component.ts
import { HelloComponent } from "./hello/hello.component";
-
修改 app.component.html
<app-hello></app-hello>
-
启动项目,访问 http://localhost:4200/,此时可以发现页面中存在 hello 组件
(3)生命周期
-
生命周期钩子及其执行顺序如下:
| 钩子 |
作用 |
时机 |
ngOnChanges() |
每当 Angular 设置或重新设置数据绑定的输入属性时响应 |
在 ngOnInit() 之前以及所绑定的一个或多个输入属性方式变化时调用 |
ngOnInit() |
在 Angular 第一次显示数据绑定和设置指令或组件的输入属性后,初始化指令或组件 |
首次 ngOnChanges() 之后调用,且仅调用一次 |
ngDoCheck() |
每当发生 Angular 无法或不愿自检的变化时,进行检测 |
每次执行变更检测时的 ngOnChanges() 之后调用 首次执行变更检测的 ngOnInit() 之后调用 |
ngAfterContentInit() |
当 Angular 将外部内容投影至组件或指令中的内容之后调用 |
首次 ngDoCheck() 之后调用,且仅调用一次 |
ngAfterContentChecked() |
每当 Angular 检测完被投影至组件或指令中的内容之后调用 |
每次 ngDoCheck() 之后调用 首次 ngAfterContentInit() 之后调用 |
ngAfterViewInit() |
当 Angular 初始化完组件视图、其子视图、或包含该指令的视图之后调用 |
首次 ngAfterContentChecked() 之后调用,且仅调用一次 |
ngAfterViewChecked() |
每当 Angular 检测完组件视图、其子视图、或包含该指令的视图的变更之后调用 |
每次 ngAfterContentChecked() 之后调用 首次 ngAfterViewInit() 之后调用 |
ngOnDestroy() |
每当 Angular 销毁指令或组件之前调用 |
在 Angular 销毁指令或组件之前立即调用 |
graph LR
ngOnChanges--首次-->ngOnInit-->ngDoCheck--首次-->ngAfterContentInit-->ngAfterContentChecked--首次-->ngAfterViewInit-->ngAfterViewChecked-.->ngOnDestroy
ngOnChanges-->ngDoCheck-->ngAfterContentChecked-->ngAfterViewChecked
-
举例:app.component.ts
export class AppComponent {
ngOnChanges() {
console.log('ngOnChanges');
}
ngOnInit() {
console.log('ngOnInit');
}
ngDoCheck() {
console.log('ngDoCheck');
}
ngAfterContentInit() {
console.log('ngAfterContentInit');
}
ngAfterContentChecked() {
console.log('ngAfterContentChecked');
}
ngAfterViewInit() {
console.log('ngAfterViewInit');
}
ngAfterViewChecked() {
console.log('ngAfterViewChecked');
}
ngOnDestroy() {
console.log('ngOnDestroy');
}
}
(4)交互
组件间传参
0x03 模板
(1)插值
-
“插值”指将表达式嵌入到 HTML 文档中,通过 {{ }} 实现
<!-- filename: app.component.html -->
<p>1 + 1 = {{ 1 + 1 }}</p>
-
在 app.component.ts 导出的类中包含的变量值,也可以通过插值语法嵌入到 HTML 文档中
(2)绑定
a. 属性绑定
b. 事件绑定
-
一般地,事件绑定使用 ( ) 实现,如:
-
对于事件对象,使用 $event 传递
c. 双向绑定
d. 模板变量
-
模板变量使用 # 声明
-
Angular 根据声明模板变量的位置进行赋值
- 在标准 HTML 元素上,则引用该元素
- 在组件上,则引用该组件实例
- 在
<ng-template> 元素上,则引用一个 TemplateRef 实例来代表模板
(3)渲染
a. 条件
I. if
使用 *ngIF 实现,使用前需要从 @angular/common 导入 NgIf
注意:仅用于元素是否被渲染,而非控制元素的显隐
-
app.component.ts
// ...
import { NgIf } from '@angular/common';
@Component({
// ...
imports: [/* ... */, NgIf],
})
export class AppComponent {
isRender = false;
}
-
app.component.html
<p>
My name is
<span *ngIf="isRender">John</span>
<span *ngIf="!isRender">SRIGT</span>
.
</p>
<!-- My name is SRIGT . -->
II. switch
使用 *ngSwitch 实现,使用前需要从 @angular/common 导入 NgSwitch
-
app.component.ts
// ...
import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
@Component({
// ...
imports: [/* ... */, NgSwitch, NgSwitchCase, NgSwitchDefault],
})
export class AppComponent {
nameType = "nickname"
}
-
app.component.html
<p [ngSwitch]="nameType">
My name is
<span *ngSwitchCase="'realname'">John</span>
<span *ngSwitchCase="'nickname'">SRIGT</span>
<span *ngSwitchDefault>Someone</span>
.
</p>
<!-- My name is SRIGT . -->
b. 循环
循环渲染使用 *ngFor 实现,使用前需要从 @angular/common 导入 NgFor
-
app.component.ts
// ...
import { NgFor } from '@angular/common';
@Component({
// ...
imports: [/* ... */, NgFor],
})
export class AppComponent {
names = ['Alex', 'Bob', 'Charlie'];
}
-
app.component.html
<div *ngFor="let name of names; let i = index; let odd = odd">
{{ odd }} {{ i }} {{ name }}
</div>
<!--
false 0 Alex
true 1 Bob
false 2 Charlie
-->
c. 控制流
Angular 16 新特性
-
@if、@else if、@else
-
@switch、@case、@default
-
app.component.ts
export class AppComponent {
nameType = "nickname"
}
-
app.component.html
<p>
My name is
@switch (nameType) {
@case ('realname') {
{{ "John" }}
}
@case ('nickname') {
{{ "SRIGT" }}
}
@default {
{{ "Someone" }}
}
}
.
</p>
<!-- My name is SRIGT . -->
-
@for
-
app.component.ts
export class AppComponent {
items = [
{ id: 1, name: 'Alex' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
];
}
-
app.component.html
<ul>
@for (item of items; track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>There are no items.</li>
}
</ul>
(4)表单
a. 单个表单控件
-
注册响应式表单模块,需要从 @angular/forms 导入 ReactiveFormsModule
// ...
import { ReactiveFormsModule } from '@angular/forms';
@Component({
// ...
imports: [/* ... */, ReactiveFormsModule],
})
export class AppComponent {}
-
生成一个新的 FormControl 实例并保存在组件中,需要从 @angular/forms 导入 FormControl
// ...
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({/* ... */})
export class AppComponent {
username = new FormControl('');
reset() {
this.username.setValue('');
}
}
-
在模板中注册该实例
<label>
username:
<input type="text" [formControl]="username" />
</label>
<p>{{ username.value }}</p>
<button (click)="reset()">reset</button>
b. 多个表单控件
-
创建 FormGroup 实例,需要从 @angular/forms 导入 FormGroup
// ...
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
@Component({/* ... */})
export class AppComponent {
loginForm = new FormGroup({
username: new FormControl(''),
password: new FormControl(''),
});
onSubmit() {
alert(
`username: ${this.loginForm.value.username}\npassword: ${this.loginForm.value.password}`
);
}
}
-
在模板中注册该实例
<form [formGroup]="loginForm">
<label>
username:
<input type="text" formControlName="username" />
</label>
<br />
<label>
password:
<input type="password" formControlName="password" />
</label>
<br />
<button (click)="onSubmit()">Login</button>
</form>
c. 表单验证
-
Angular 支持 HTML5 提供的表单验证关键字(如 required 等),通过双向绑定可以获取组件信息,从得到验证结果
-
app.component.ts
// ...
import {
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
Validators,
} from '@angular/forms';
@Component({
// ...
imports: [/* ... */, ReactiveFormsModule],
})
export class AppComponent {
// 表单数据
formData = { username: '', password: '' };
// 自定义验证方法
passwordValidator(control: FormControl): ValidationErrors | null {
return control.value.length < 5 ? { password: true } : null;
}
// 表单控件组
loginForm = new FormGroup({
username: new FormControl(this.formData.username, [
Validators.maxLength(10),
]),
password: new FormControl(this.formData.password, [
Validators.required,
this.passwordValidator,
]),
});
// 提交方法
onSubmit() {
alert(
`username: ${this.loginForm.value.username}\npassword: ${this.loginForm.value.password}`
);
}
}
-
app.component.html
<form [formGroup]="loginForm">
<label>
username:
<input type="text" formControlName="username" required />
</label>
<br />
<span>{{ loginForm.get("username")?.valid }}</span>
<br />
<label>
password:
<input type="password" formControlName="password" />
</label>
<br />
<span>{{ loginForm.get("password")?.valid }}</span>
<br />
<button (click)="onSubmit()">Login</button>
</form>
其中,.valid 是布尔类型
-
ngModel 可用于跟踪修改状态与有效性验证,通过三个 CSS 类来更新控件:
| 状态 |
为真 |
为假 |
| 已被访问 |
ng-touched |
ug-untouched |
| 已变化 |
ng-dirty |
ng-pristine |
| 有效 |
ng-valid |
ng-invalid |
input {
outline: none;
}
input.ng-invalid {
border: 1px solid red;
}
(5)管道
- 管道用于传输,即数据处理
- 管道采用链式连接,自左向右,依次执行
- 语法格式:
{{ 输入数据 | 管道1:参数 | 管道2... }}
注意:管道操作符的优先级高于 JavaScript 三元运算符 ?:,同时使用时需要使用 ( ) 包裹三元运算符部分
a. 内置管道
-
内置管道包括
| 管道 |
功能 |
date |
格式化日期 |
json |
经过 JSON.stringify() |
uppercase |
字母大写转换 |
lowercase |
字母小写转换 |
decimal |
数值特定格式 |
currectcy |
数值货币格式 |
percent |
数值百分比格式 |
slice |
切片成新子集 |
-
举例:
-
app.component.ts
// ...
import { CommonModule } from '@angular/common';
@Component({
// ...
imports: [/* ... */, CommonModule],
})
export class AppComponent {
today: Date = new Date();
}
-
app.component.html
<p>{{ today | date: 'yyyy-MM-dd HH:mm:ss'}}</p>
b. 自定义管道
使用命令 ng g p newPipe 生成名为 newPipe 的自定义管道,通过 @Pipe 装饰器标识管道
-
prefix.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'prefix',
standalone: true,
})
export class PrefixPipe implements PipeTransform {
transform(value: string, ...args: unknown[]): unknown {
return `angular-${value}`;
}
}
-
app.component.ts
// ...
import { CommonModule } from '@angular/common';
import { PrefixPipe } from './prefix.pipe';
@Component({
// ...
imports: [/* ... */, CommonModule, PrefixPipe],
})
export class AppComponent {}
-
app.component.html
<p>{{ "SRIGT" | prefix }}</p>
0x04 依赖注入
(1)服务
-
从组件中抽离出来的代码成为服务,其本质上是函数
- 主要将数据访问的职责从组件中独立成服务,使组件专注于数据展示
-
使用命令 ng g s list 生成一个名为 list 的服务,通过 @Injectable 装饰器标识服务
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ListService {
constructor() {}
}
-
服务需要作为依赖,注入到系统、组件或模块才能使用,通过注册提供商和根注入器实现
(2)依赖注入
0x05 路由
(1)配置
-
路由器(Router)是一套规则列表,可用于查询 URL 对应的视图规则
-
路由(Route)是列表中的一条规则
path:URL 目标
component:对应视图(组件)
-
一般地,路由规则配置在 .routes.ts 文件中,如 app.routes.ts:
import { Routes } from '@angular/router';
export const routes: Routes = [];
-
举例,设置链接,从 / 跳转至 /hello:
-
app.routes.ts
import { Routes } from '@angular/router';
import { HelloComponent } from './hello/hello.component';
export const routes: Routes = [
{
path: 'hello',
component: HelloComponent,
},
];
-
app.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {}
-
app.component.html
<a [routerLink]="['/hello']">Hello</a>
<router-outlet></router-outlet>
-
hello.component.html
<p>Hello, Angular</p>
-
使用通配路由实现 404 页面
export const routes: Routes = [
// ...
{
path: '**',
component: NotFoundComponent,
},
];
(2)嵌套
-
路由规则的 children 属性可用于实现路由嵌套
// filename: app.routes.ts
import { Routes } from '@angular/router';
import { HelloComponent } from './hello/hello.component';
export const routes: Routes = [
{
path: 'hello',
component: HelloComponent,
children: [
{
path: 'angular',
component: HelloComponent,
},
],
},
];
-
在非父级路由 /hello 需要跳转到 /hello/angular 时,需要些完整路径
<!-- filename: app.component.html -->
<a [routerLink]="['/hello/angular']">Hello</a>
<router-outlet></router-outlet>
(3)传参
更多详细内容参考官方文档
-End-
来源:https://www.cnblogs.com/SRIGT/p/18520352 |