Angular Basic Knowledge Points
Angular - From Beginner to Abandonment
2025-10-30
项目结构
my-app/ # 项目根目录
├── e2e/ # 端到端(E2E)测试目录
│ ├── src/ # E2E测试源码目录
│ │ └── e2e.ts # E2E测试入口文件
│ └── tsconfig.e2e.json # E2E测试的TypeScript配置文件
├── node_modules/ # 项目依赖包目录(npm install生成)
├── projects/ # 多项目 workspace 目录,存放多个Angular应用/库
├── src/ # 主应用源码目录
│ ├── assets/ # 静态资源目录(图片、字体、配置文件等)
│ ├── environments/ # 环境配置目录
│ │ ├── environment.ts # 开发环境配置(默认)
│ │ └── environment.prod.ts # 生产环境配置
│ ├── browser/ # 浏览器端相关资源(如适配特定浏览器的脚本)
│ ├── app/ # 应用核心代码目录
│ │ ├── core/ # 核心模块目录(单例服务、全局配置,仅根模块导入)
│ │ │ ├── Services/ # 全局核心服务(如认证、HTTP请求、权限管理)
│ │ │ ├── Interceptors/ # HTTP拦截器(请求头添加、响应错误处理)
│ │ │ ├── Guards/ # 路由守卫(权限控制、导航拦截)
│ │ │ ├── Models/ # 全局核心数据模型(如用户信息、全局配置接口)
│ │ │ ├── Pipes/ # 全局核心管道(如脱敏、特殊格式转换)
│ │ │ ├── Directives/ # 全局核心指令(如自定义表单验证、DOM操作指令)
│ │ │ └── Utilities/ # 全局工具类(如日期处理、加密、字符串工具)
│ │ ├── shared/ # 共享模块目录(通用组件/服务,可被多个业务模块导入)
│ │ │ ├── Components/ # 通用UI组件(如按钮、模态框、分页、表格)
│ │ │ ├── Services/ # 共享辅助服务(如通知、加载动画、本地存储)
│ │ │ ├── Pipes/ # 共享管道(如日期格式化、货币转换、中英文切换)
│ │ │ ├── Directives/ # 共享指令(如防抖点击、输入框限制)
│ │ │ ├── Models/ # 共享数据模型(如分页参数、通用返回格式)
│ │ │ └── Utilities/ # 共享工具类(如防抖、节流、数据校验)
│ │ ├── app.component.html # 根组件模板
│ │ ├── app.component.scss # 根组件样式(按规范补充.scss后缀)
│ │ ├── app.component.ts # 根组件逻辑代码
│ │ ├── app.component.spec.ts # 根组件单元测试文件
│ │ └── app.module.ts # 根模块(应用入口模块,声明根组件、导入核心/共享模块)
│ ├── main.ts # 应用入口文件(引导根模块启动)
│ ├── polyfills.ts # 兼容性补丁文件(适配低版本浏览器)
│ ├── styles.scss # 全局样式文件(应用所有组件共享)
│ ├── test.ts # 单元测试入口文件
│ ├── index.html # 应用入口HTML(Angular会注入编译后的代码)
│ ├── tsconfig.app.json # 应用代码的TypeScript配置
│ └── tsconfig.spec.json # 测试代码的TypeScript配置
├── angular.json # Angular CLI核心配置文件(项目结构、构建/打包配置)
├── karma.conf.js # Karma测试运行器配置(单元测试环境)
├── package.json # 项目依赖配置(npm包、脚本命令如start/build/test)
├── package-lock.json # 依赖版本锁定文件(确保团队依赖一致)
├── README.md # 项目说明文档(安装、启动、部署步骤)
├── tsconfig.json # 项目全局TypeScript配置(统一下游tsconfig.app.json等)
└── tslint.json # TSLint代码规范检查配置(已逐步被eslint替代)
组件 (xxx.component.ts)
负责 UI 的显示和逻辑处理。
处理用户交互事件。
管理局部状态。
模块 (xxx.module.ts)
用于组织和配置应用的结构。模块是用来组织组件、指令和服务等的一组集合。通过模块,你可以定义应用的结构和配置,使用 @NgModule
声明组件和其他可重用的类。
导入其他模块提供的功能。
导出模块内的组件以便在其他地方使用。
路由模块 (xxx-routing.module.ts)
用于管理应用的导航和 URL 路由规则。
导入 RouterModule 并使用 forChild 或 forRoot 方法配置路由。
导出 RouterModule 以便其他模块可以使用
基本语法
- https://cloud.tencent.com/developer/article/1767882
- https://angular.cn/tutorials/learn-angular/12-enable-routing
- https://juejin.cn/post/7028957117839048712
import {Component} from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
// 判断 (控制流语句,新语法) 也可以使用 *ngIf
@if (isLoggedIn) {
<p>Welcome back, Friend!</p>
}
@else { ... }
// 循环 (控制流语句) 也可以使用 *ngFor
@for(item of array, track item.id){
{{item.name}}
// 绑定方法
<button type="button" (click)="deleteHero(hero)">{{ hero.name }}</button>
}
`,
//另一种写法(推荐)
templateUrl: './xxx.component.html',
})
export class AppComponent {
// 定义变量(可以使用 private 和 public 修饰符,不写,默认为 public)
isLoggedIn = true
}
- 模板变量:使用 # 定义,使用时,直接用该名称
<!-- 定义 -->
<input #phone placeholder="phone number" />
---
<!-- 使用 -->
<!-- phone refers to the input element; pass its `value` to an event handler -->
<button type="button" (click)="callPhone(phone.value)">Call</button>
- 属性绑定:将属性名称用方括号括起来
<img alt="photo" [src]="imageURL">
- 双向数据绑定 (ngModel): 同时绑定输入和输出,常用于表单控件
import {FormsModule} from '@angular/forms';
---
<input [(ngModel)]="username">
- 事件绑定:使用圆括号语法 () 绑定事件
<button type="button" (click)="deleteHero(hero)">{{ hero.name }}</button>
- 插槽
与 原生 元素类似 - 是一个特殊的占位符,用于告诉 Angular 在哪里渲染内容
- Angular 的编译器在构建时会处理所有
元素 - 你不能在运行时插入、删除或修改
- 你不能向
添加 指令、样式或任意属性 - 多个内容占位符,在
中使用 select 属性,相当于起新的标签名
<ng-content select="card-body"></ng-content>
<!-- 然后就可以使用这个名称作为组件 : <card-body /> -->
- 管道(Pipes)
- 类似于 Vue2 中的 过滤器
- 管道是用于在模板中转换数据的函数。一般来说,管道是不会产生副作用的「纯」函数
- 管道是一种用于将数据转换为特定格式的机制,例如日期、货币和百分比、大写、小写
import { DatePipe } from '@angular/common';
@Component({
imports: [ DatePipe ]
})
<div class="left-timestamp">{{ message.timestamp | date: 'yyyy-MM-dd HH:mm' }}</div>
// 自定义管道
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'customPipe',
pure: true // 默认为 true
})
export class CustomPipe implements PipeTransform {
transform(value: string, prefix: string): string {
return `${prefix}: ${value}`;
}
}
- 服务 (Services)
- 用于封装业务逻辑,如数据获取、认证等。
- 通常使用 @Injectable 装饰器定义,并通过依赖注入系统在整个应用中共享。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MyService {
constructor() { }
// 服务的方法
doSomething(): void {
console.log('Doing something...');
}
}
import { Component } from '@angular/core';
import { MyService } from './my.service';
import { Component, inject,TemplateRef,ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `
<p>Component using the service.</p>
`,
})
export class MyComponent {
// 第一种方式
constructor(private myService: MyService) {
this.myService.doSomething();
}
// 第二种方式
// 使用 inject 函数 (推荐) / @inject(装饰器)
private myService = inject(MyService)
// 模板引用,用于访问模板。
private templateRef = inject(TemplateRef)
// 视图容器引用,用于插入或清除视图。
private viewContainerRef = inject(ViewContainerRef)
......
onChangeHandle(){
this.myService.doSomething()
}
}
- 模块 (Modules)
- Angular应用由模块组成,模块定义了一组相关的组件和服务。
- 使用 @NgModule 装饰器来定义模块。
- 模块可以导入其他模块,从而实现功能的组合和复用
- 路由 (Routing)
- 用于处理应用内的导航和URL映射。
- 使用 RouterModule 和 Routes 配置路由。
- 依赖注入 (Dependency Injection)
- Angular使用依赖注入系统来管理组件和服务的生命周期。
- 依赖关系在模块的 providers 数组中声明
- 图片优化
- NgOptimizedImage 指令
- 是 Angular 15 引入的一个强大工具,可以帮助开发者轻松实现图像优化。
- 通过响应式图像加载、懒加载、图像格式优化、错误处理和加载指示器等功能,可以显著提升应用的性能和用户体验
import { Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
@Component({
selector: 'app-root',
template: `
<div>
<h1>NgOptimizedImage 示例</h1>
<img
src="assets/images/image-320w.jpg"
srcset="assets/images/image-320w.jpg 320w, assets/images/image-640w.jpg 640w"
sizes="(max-width: 640px) 320px, 640px"
ngOptimizedImage
error="assets/images/error-image.jpg"
loading="assets/images/loading-indicator.gif"
/>
</div>
`,
imports:[NgOptimizedImage]
})
export class AppComponent {}
Angular 指令
- 组件指令 (Component Directives)
- 每个组件都有自己的视图和逻辑。组件通过 @Component 装饰器定义,并且必须至少有一个模板。
@Component({
selector: 'app-websocket-chat',
templateUrl: './websocket-chat.component.html',
styleUrls: ['./websocket-chat.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [ NzInputModule]
})
- 属性型指令 (Attribute Directives)
- 改变现有元素的外观或行为,但不改变其结构。它们通过添加或修改DOM元素的属性或添加事件监听器来工作,可以响应数据绑定或用户交互
- ngClass 用于动态添加CSS类,通常以ng前缀开始,但在实际使用时可以省略这个前缀。
- NgClass 添加和删除一组 CSS 类。
- NgStyle 添加和删除一组 HTML 样式。
- (NgModel) 为 HTML 表单元素添加双向数据绑定。
- 自定义属性型指令通常通过实现 Directive类 并使用 @Directive 装饰器来定义
- 结构型指令(Structural Directives) (v18版本已很少使用,可以使用 控制流语句)
- 用于改变DOM的结构,即它们可以添加、删除或移动DOM元素
- *ngIf 根据条件添加或移除 DOM元素,为 true 显示,为 假或者 null 则不显示
- 使用 @if @else 代替
- *ngFor 重复渲染一个列表的每个项,简写形式 : let item of items
<ng-container *ngFor="let item of list?.dependencies | keyvalue">
<nz-descriptions-item [nzTitle]="item.key">
{{ item.value }}
</nz-descriptions-item>
</ng-container>
<!-- 使用 @for 代替 , 推荐 -->
@for (theme of themes; track $index) {
.................
}
Angular 生命周期
- 使用生命周期时,需要先引入对应生命周期,然后 使用
implements实现对应的生命周期接口 - 这是因为生命周期钩子方法(如 ngOnInit, ngAfterViewInit 等)是定义在特定的生命周期接口中的
import { OnInit } from '@angular/core';
export class AppComponent implements OnInit {
......
ngOnInit(): void {
this.analytics.trackPageViews();
this.seoService.trackCanonicalChanges();
}
}
- constructor --> ngOnChanges --> ngOnInit(只调用一次) --> ngDoCheck --> ngAfterContentInit(只调用一次) --> ngAfterContentCheckd --> ngOnViewInit(只调用一次) --> ngOnViewChecked --> ngOnDestroy
生命周期(10个):
ngOnChanges()
作用:当组件的输入属性(通过 @Input() 装饰器标记的属性)发生变化时调用。
调用时机:在首次渲染前,以及后续每次输入属性更新时。
@Input() purChoosePeople: LazySelPeopleEnum = LazySelPeopleEnum.YiLin;
@Input() purChooseAnimal: LazySelAnimalEnum = LazySelAnimalEnum.YiLin;
.....................................................................
ngOnChanges(changes: SimpleChanges): void {
console.log(changes[purChoosePeople]);
console.log(changes[purChooseAnimal]);
}
ngOnInit()
类似于 Vue 中的 mounted() 、React 中的 useEffect()
作用:在组件初始化时调用,通常用于执行一次性的初始化操作,如订阅服务、加载数据等。
调用时机:在首次 ngOnChanges() 执行后调用,保证了所有输入属性都已经有了初始值。
ngDoCheck()
作用:自定义检测逻辑,用于检查组件内部状态的更改,可以用于检测 Angular 自身无法检测的变化。
调用时机:在每个变更检测周期中调用,即每次 Angular 运行变更检测时。
ngAfterContentInit()
作用:在组件的内容(通过 contentChild 、contentChildren | @ContentChild 或 @ContentChildren 标记的指令)初始化完成后调用。
这个钩子非常适合用于执行依赖于内容初始化的操作,包括 DOM 操作。
默认情况下,contentChildren 查询仅查找组件的直接子项,而不遍历后代。 contentChild 查询默认会遍历后代
调用时机:在首次 ngDoCheck() 调用后,确保了组件视图中的投影内容已经可用。只调用一次
ngAfterContentChecked()
作用:每当 Angular 完成被投影组件内容的变更检测之后调用。
调用时机: ngAfterContentInit() 和每次 ngDoCheck() 之后调用
ngAfterViewInit()
作用:在组件视图及其子视图初始化完成后调用
适合于访问组件视图中的本地元素引用 ( viewChild 和 viewChildren | @ViewChild 和 @ViewChildren)。
调用时机:在首次 ngAfterContentInit() 调用后,确保了组件及其子组件的视图已经完全初始化。只调用一次
ngAfterViewChecked()
作用:在每次变更检测周期结束后调用,可以用来检查视图是否与模型一致。
调用时机:在每次 ngDoCheck()
ngOnDestroy()
作用:在组件即将被销毁前调用,通常用于清理资源,如取消订阅、释放定时器等。
调用时机:在组件销毁前调用。
-------------------------------------------------------------------------------------------------------------------------------
ngOnChanges()
当 Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的 SimpleChanges 对象,
意发生的非常频繁,所以你在这里执行的任何操作都会显著影响性能
在 ngOnInit() 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用
如果组件绑定过输入属性,那么在 ngOnInit() 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。 注意,如果你的组件没有输入属性,或者你使用它时没有提供任何输入属性,那么框架就不会调用
.......... .......... .......... ..........
@Input() password: string | undefined;
ngOnChanges(changes: SimpleChanges): void {
if (changes['password']) {
this.calculatePasswordStrength();
}
}
ngOnInit()
在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。
在第一轮 ngOnChanges() 完成之后调用,只调用一次。[请求数据时使用]
ngDoCheck()
检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。
在每个变更检测周期中,紧跟在 ngOnChanges() 和 ngOnInit() 后面调用。
ngAfterContentInit() 用于 DOM 操作
当 Angular 把外部内容投影进组件/指令的视图之后调用。
第一次 ngDoCheck() 之后调用,只调用一次。
在首次内容投影完成后调用。当组件使用 @ContentChild 或 @ContentChildren 时有用。
ngAfterContentChecked()
每当 Angular 完成被投影组件内容的变更检测之后调用。
ngAfterContentInit() 和每次 ngDoCheck() 之后调用
ngAfterViewInit() 用于 DOM 操作
在视图初始化完成后调用。适合于访问组件视图中的本地元素引用 (@ViewChild 和 @ViewChildren)。
当 Angular 初始化完组件视图及其子视图之后调用。
第一次 ngAfterContentChecked() 之后调用,只调用一次
ngAfterViewChecked()
每当 Angular 做完组件视图和子视图的变更检测之后调用。
ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。
ngOnDestroy()
每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。
在 Angular 销毁指令/组件之前调用。
Angular 装饰器
@NgModule (Angular v14 版本以后已很少使用,直接在组件中 import 就行)
作用:定义一个模块,用于组织组件和服务。
属性:
declarations: 声明属于该模块的组件和指令。
imports: 导入其他模块。
exports: 导出可以在其他模块中使用的组件和指令。
providers: 提供服务。
@Directive
作用:定义一个自定义指令。
属性:
selector: 指令的选择器。
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {}
@Input
作用:标记一个属性为输入属性,允许从父组件传递数据。
注: 最新版本推荐使用基于信号的 input 函数、基于双向绑定则使用 model 函数
@Component({
selector: 'app-child',
template: `<p>{{ message }}</p>`
})
export class ChildComponent {
@Input() message: string;
}
@Output
作用:标记一个属性为输出属性,允许向父组件发送事件。
注: 最新版本推荐使用基于信号的 output 函数
@Component({
selector: 'app-child',
template: `<button (click)="onClick()">Click me</button>`
})
export class ChildComponent {
@Output() clicked = new EventEmitter<string>();
onClick() {
this.clicked.emit('Button clicked');
}
}
@HostBinding
作用:绑定宿主元素的属性、类或样式
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@HostBinding('style.backgroundColor') backgroundColor = 'yellow';
}
@HostListener
作用:监听宿主元素的事件。
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@HostListener('mouseenter') onMouseEnter() {
this.backgroundColor = 'yellow';
}
@HostListener('mouseleave') onMouseLeave() {
this.backgroundColor = '';
}
}
@Pipe
作用:定义一个自定义管道。管道(相当于 Vue 的过滤器):引入之后,使用 | 符号
属性:
name: 管道的名称
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'uppercase'
})
export class UppercasePipe implements PipeTransform {
transform(value: string): string {
return value.toUpperCase();
}
}
@Injectable()
作用:
用于标记一个类,使其可以通过依赖注入系统被实例化。
依赖注入(Dependency Injection, DI)是 Angular 的核心功能之一,
它可以帮助管理组件和服务之间的依赖关系,使得代码更加模块化和可维护。
用于标记服务类,使其成为 Angular 依赖注入系统的一部分。
通常用于服务类,但也可以用于其他类。
可以指定服务的提供范围。
属性:
providedIn 属性,可以指定服务的提供范围,
例如全局范围('root')或特定模块范围(AppModule)
@Injectable({
providedIn: 'root' // 或者指定一个模块
})
@Inject: (新版本可以使用 inject 函数 代替)
用于显式指定依赖项的令牌。
通常用于 constructor 参数。
用于注入非类类型的依赖项
从 Angular 14 开始,引入了一个新的 inject 函数,
它提供了一种更简洁的方式来注入依赖项。
这个函数允许你直接在 constructor 中使用依赖项而无需显式地使用 @Inject 装饰器
内容查询:
内容查询结果在生命周期方法 ngAfterContentInit 中变为可用。在此之前,该值为 undefined
从父组件中选择子组件或指令。这两个装饰器主要用于处理投影的内容(即通过 `<ng-content>` 投影进来的子视图
@ContentChild
用于选择单个子组件或指令实例。它接受一个选项对象来指定查询的条件,比如读取特定的属性
用于在组件或指令中查询和获取投影到组件中的内容
即通过 <ng-content> 投影的内容)中的某个特定元素或指令实例。
@Component({
selector: 'app-card-table-wrap',
template: `<div><ng-content></ng-content></div>`
})
export class CardTableWrapComponent {
@ContentChild('myTemplate') myTemplate: TemplateRef<any>;
}
@ContentChildren
用于选择多个子组件或指令实例。
它返回的是一个 QueryList,这是一个可观察的列表,当视图发生变化时会自动更新
@Component({
selector: 'app-card-table-wrap',
template: `<div><ng-content></ng-content></div>`
})
export class CardTableWrapComponent {
@ContentChildren('myTemplate') myTemplates: QueryList<TemplateRef<any>>;
}
建议在 ngAfterContentInit 或 ngAfterContentChecked 生命周期钩子中使用这些装饰器获取的数据。
如果需要在构造函数中立即使用这些数据,可以考虑使用 AfterContentInit 或 AfterContentChecked 接口,并在相应的生命周期钩子方法中访问这些数据
视图查询:
视图查询结果在 ngAfterViewInit 生命周期方法中变为可用。在此之前,该值为 undefined。
允许你在组件类中访问模板内的子视图中的指令、组件实例或 DOM 元素
@ViewChild
注:最新的版本中推荐使用基于信号的 viewChild 函数
查询组件模板中的某个特定元素或指令实例
用于从父组件中选择单个子视图中的组件实例或 DOM 元素。它通常用于访问模板内的子组件或元素
查询的结果通常在 ngAfterViewInit 生命周期钩子中可用
@Component({
selector: 'app-parent',
template: `<app-child *ngFor="let item of items" #childRef></app-child>`
})
export class ParentComponent {
@ViewChildren('childRef') childRefs: QueryList<ChildComponent>;
}
@ViewChild (***)
用于获取单个子视图中的组件实例或 DOM 元素。它可以接受一个选项对象来指定查询的条件,比如读取特定的属性
@ViewChildren
用于获取多个子视图中的组件实例或 DOM 元素,它返回的是一个 QueryList,
这是一个可观察的列表,当视图发生变化时会自动更新
Angular 路由
import { Router, RouterLink } from '@angular/router';
..............
impores:[
RouterLink
]
private router = inject(Router);
..............
路由方法:
// navigate 方法用于导航到指定的路由。它可以接受一个数组作为参数,数组中的第一个元素是路由路径,后续元素是路由参数。
// 示例方法:带参数的导航
navigateToUser(userId: string) {
this.router.navigate(['/user', userId]);
}
// 示例方法:相对路径导航
navigateRelative() {
this.router.navigate(['../sibling'], { relativeTo: this.route });
}
queryParams:用于传递查询参数。
fragment:用于指定 URL 的片段标识符。
relativeTo:用于指定相对路径的起点
this.router.navigate(['/user', '123'], {
queryParams: { tab: 'profile' },
fragment: 'anchor',
relativeTo: this.route
});
// navigateByUrl 方法用于通过完整的 URL 字符串进行导航。
this.router.navigateByUrl('login/login-form');
<a class="password-login" routerLink="/login/login-form">使用已有帐号登陆
ChangeDetectorRef
- 当异步获取数据后,想要立即更新视图,可以使用这个,有点类似于 Vue3 中的 nextick, react 中的 useEffect
import { inject, ChangeDetectorRef } from '@angular/core';
.........................................
private cdr = inject(ChangeDetectorRef);
........................................
// 评论列表
getCommentsList(): void {
this.commentsService
.getCommentsList()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(res => {
this.listData = res;
this.cdr.detectChanges(); // 手动触发变更检测
});
}
// 立即触发一次变更检测,适用于局部组件。
detectChanges()
// 标记当前组件及其祖先组件在下一个变更检测周期中进行检查。适用于 OnPush 策略的组件
markForCheck()
// 将当前组件从变更检测树中分离,直到重新附加。适用于性能优化,避免不必要的变更检测。
detach()
// 将当前组件重新附加到变更检测树中。
reattach()
// 检查当前组件是否有未处理的变化。如果发现未处理的变化,会抛出异常。主要用于调试。
checkNoChanges()
常见使用场景:
1.异步操作后更新视图: 使用 detectChanges()
2.OnPush 策略的组件:使用 markForCheck()
当组件使用 OnPush 变更检测策略时,只有当输入属性发生变化或事件处理器被调用时才会进行变更检测。在这种情况下,如果组件内部发生了变化,需要手动触发变更检测。
import { Component, ChangeDetectionStrategy,ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-on-push-component',
template: `<p>{{ message }}</p>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
message: string;
constructor(private cdr: ChangeDetectorRef) {
this.message = 'Initial message';
}
updateMessage() {
this.message = 'Updated message';
this.cdr.markForCheck(); // 标记组件进行变更检测
}
}
3.性能优化
在某些高性能要求的场景下,可以暂时将组件从变更检测树中分离,以避免不必要的变更检测。
import { Component, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-performance-component',
template: `<p>{{ message }}</p>`
})
export class PerformanceComponent {
message: string;
constructor(private cdr: ChangeDetectorRef) {
this.message = 'Initial message';
}
startHeavyOperation() {
this.cdr.detach(); // 分离组件
// 执行耗时操作
setTimeout(() => {
this.message = 'Operation completed';
this.cdr.reattach(); // 重新附加组件
this.cdr.detectChanges(); // 手动触发变更检测
}, 5000);
}
}
Angular 监听路由变化
- 可以使用 ActivatedRoute 服务来监听路由变化
import { Component, OnInit,inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnInit {
private route = inject(ActivatedRoute)
ngOnInit() {
this.route.params.subscribe(params => {
console.log('Params:', params);
});
this.route.queryParams.subscribe(queryParams => {
console.log('Query Params:', queryParams);
});
this.route.fragment.subscribe(fragment => {
console.log('Fragment:', fragment);
});
}
}
Angular 组件通信
1. 父子组件通信 (两个装饰器都是子组件使用)
@Input(): 类似于 props
父组件中传递,子组件中使用 @Input()接收
@Output(): 从子组件到父组件的通信路径,请在类属性上使用 @Output 装饰器,并将其赋值为 EventEmitter 类型
// 子组件
@Component({
...
template: '<button (click)="sendMessage()">Send Message</button>'
})
class ChildComponent {
@Output() messageEvent = new EventEmitter<string>();
sendMessage() {
this.messageEvent.emit('Hello from child!');
}
}
// 父组件
<app-child (messageEvent)="receiveMessage($event)"></app-child>
2. 兄弟组件通信
共享服务 (Shared Service): 可以创建一个服务并通过依赖注入来共享数据或方法。
Injectable
Inject
BehaviorSubject
RxJS Observables: 使用 RxJS 的 BehaviorSubject 或 ReplaySubject 等来传递数据。
3. 跨层级组件通信
共享服务 (Shared Service): 适用于任何层级的组件之间的通信。
RxJS Observables: 同样适用于任何层级的组件之间的通信。
4. 直接访问子组件
ViewChild 或 ContentChild: 父组件可以直接访问子组件的实例。
5. Angular 14以后新增 context
import { createContext, useContext } from '@angular/core';
const AppContext = createContext('App context');
@Component({
selector: 'app-root',
template: `<app-child></app-child>`
})
export class AppComponent {
context = useContext(AppContext, 'Initial context value');
}
@Component({
selector: 'app-child',
template: `<div>{{ context }}</div>`
})
export class ChildComponent {
context = useContext(AppContext);
}
6. Angular Router
通过路由参数:
使用路由配置来传递参数。
示例:
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: 'detail/:id', component: DetailComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
在组件中使用:
使用 ActivatedRoute 获取路由参数。
示例:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-detail',
template: `<div>Detail ID: {{ id }}</div>`
})
export class DetailComponent implements OnInit {
id: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.id = this.route.snapshot.paramMap.get('id');
}
}
Angular 中 实现类似于 Vue 中监听器的功能
使用 RxJS 的 Subject: 通过创建一个 BehaviorSubject 来共享状态,并在父组件中订阅这个状态
1. 创建一个 BehaviorSubject 来共享状态。
子组件通过 updateSharedValue 方法更新状态。
父组件通过订阅 sharedValue$ 来监听值的变化。
2. 使用 @Output 和事件:
子组件通过 @Output 发射事件。
父组件通过 (valueChanged) 绑定事件处理器来监听值的变化。
使用 RxJS 的 Subject:
// 可以 定义一个 Service
// 创建一个 BehaviorSubject 来共享状态
// SharedData.Service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SharedDataService {
private _sharedValue = new BehaviorSubject<string>('Initial Value');
sharedValue$ = this._sharedValue.asObservable();
constructor() {}
updateSharedValue(value: string) {
this._sharedValue.next(value);
}
}
-----------------------------------------------------------------------------------------
// 子组件
import { Component, OnInit } from '@angular/core';
import { SharedDataService } from '../services/shared-data.service';
@Component({
selector: 'app-child',
template: `
<input [(ngModel)]="inputValue" (ngModelChange)="onInputChange($event)" />
`
})
export class ChildComponent implements OnInit {
inputValue = '';
constructor(private sharedData: SharedDataService) {}
// 用于处理输入框 (<input>) 的值发生变化时的逻辑
onInputChange(event: any) {
this.inputValue = event.target.value;
this.sharedData.updateSharedValue(this.inputValue);
}
ngOnInit() {
console.log('ChildComponent initialized');
}
}
-----------------------------------------------------------------------------------------
// 父组件
import { Component, OnInit } from '@angular/core';
import { SharedDataService } from '../services/shared-data.service';
@Component({
selector: 'app-parent',
template: `
<div>
<p>Parent Value: {{ parentValue }}</p>
<app-child></app-child>
<button (click)="updateValue()">Update Value</button>
</div>
`
})
export class ParentComponent implements OnInit {
parentValue = '';
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.sharedValue$.subscribe(value => {
this.parentValue = value;
console.log('Value changed:', value);
});
}
updateValue(): void {
this.sharedData.updateSharedValue('New Value');
}
}
Angular 中 constructor 的作用
1. 依赖注入 (Dependency Injection):
constructor 是 Angular 的依赖注入系统识别依赖项的主要位置。当你在 constructor 中声明了一个服务或其他类的参数时,Angular 的依赖注入系统会自动解析这些依赖并将其实例化对象注入到构造函数中。
例如,如果你有一个服务 MyService,你可以在组件的 constructor 中声明它,Angular 会自动为你注入这个服务的实例。
2. 初始化工作:
constructor 也可以用来做一些基本的初始化工作,比如设置一些初始值或者调用服务中的方法来获取初始数据。
但是,一般建议不要在 constructor 中执行复杂的逻辑或副作用操作,因为这可能会导致代码难以理解和维护。
3. 私有化成员变量:
有时你可能希望某些成员变量只在类内部可见。虽然 TypeScript 和 ES6 不支持私有构造函数,但你可以利用 constructor 来初始化私有成员变量。
例如,你可以在 constructor 中初始化私有变量,并在类内部使用这些变量。
为什么在服务类中使用 constructor?
1. 依赖注入:
在服务类中使用 constructor 的主要原因是进行依赖注入。服务通常需要与其他服务或外部资源交互,因此需要在 constructor 中声明这些依赖。
例如,一个服务可能需要从 HTTP 客户端获取数据,这时你可以在服务的 constructor 中注入 HttpClient。
2. 初始化:
服务类中的 constructor 也可以用来初始化一些基本的状态或设置默认值。
总结起来,constructor 在 Angular 中主要用于依赖注入和初始化工作。对于服务类而言,依赖注入尤为重要,因为它使得服务可以轻松地与其他服务或外部资源进行交互,同时也提高了代码的可测试性和可维护性。
Angular v14 到 v18 的变化
1. 去除 @NgModule
改为 可选的功能
2. 去除 Decorator(装饰器)
例如 @InJect 改为 InJect()函数
3. 去除 结构型指令
4. 引入新的控制流指令
@if
@for
@switch
RxJS
RxJS是一种流式编程库,它允许您使用 Observable 对象来处理异步和事件驱动的编程任务。
在Angular中,RxJS常用于处理HTTP请求、数据流和事件处理等任务
Angular 本身就大量依赖于 RxJS 来处理各种异步任务,如 HTTP 请求、表单验证、状态管理等。
核心概念
1. Observable(可观察对象):
它是一个异步数据流的发布者。
当创建了一个 Observable 后,你可以订阅它来接收数据流中的值。
Observable 可以发射多个值,并且可以在任何时候完成或者错误。
2. Observer(观察者):
它是接收 Observable 发射的数据的对象。
Observer 有三个方法:next、error 和 complete。
next 方法接收 Observable 发出的正常值。
error 方法接收 Observable 发生的错误。
complete 方法在 Observable 完成时调用。
3. Subscription(订阅):
订阅是 Observer 和 Observable 之间的连接。
当你订阅一个 Observable 时,你会得到一个 Subscription 对象,它有一个 unsubscribe 方法可以用来取消订阅。
4. Operators(操作符):
操作符是用来处理 Observable 数据流的方法。
例如,map 用于转换数据,filter 用于过滤数据,concatMap 用于扁平化数据流等。
应用场景:
HTTP 请求:Angular 的 HttpClient 模块返回的是 Observable,可以方便地处理异步数据。
表单验证:FormsModule 和 ReactiveFormsModule 提供了基于 Observable 的表单验证机制。
状态管理:虽然 Angular 自身没有内置状态管理库,但 RxJS 可以用来构建简单的状态管理解决方案。
路由守卫:如 canDeactivate 守卫,可以使用 RxJS 来处理异步逻辑,决定是否允许用户离开当前路由。
服务间通信:使用 Subjects 或 BehaviorSubjects 来在组件和服务之间共享数据。
Angular 路由守卫
1. CanActivate(*):在激活路由前执行,用于决定是否允许导航到该路由、用于检查用户是否有权限访问该路由
2. CanActivateChild:在子路由激活之前执行逻辑,用于验证用户是否有权限访问子路由。
3. CanDeactivate(*):在离开当前路由之前执行,用于决定是否允许离开当前路由。
4. Resolve(*):在激活路由前执行,用于在导航完成前获取数据、用于预加载数据
5. CanLoad(*):在懒加载模块前执行,用于控制模块是否可以被加载
6. CanMatch:在匹配路由前执行,用于控制路由是否可以被匹配
实现方式:
路由守卫通常是一个实现了特定接口的类。
需要在路由配置中注册这些守卫。
Angular 的变化检测机制
类似于 diff 算法
1. Angular 使用了一种称为“脏检查”的机制来进行变化检测。确定组件的状态是否发生了变化。当应用程序状态发生变化时,Angular 会遍历组件树来检查是否有任何需要更新的视图。
2. Angular 通过 DefaultKeyValueDiffer 和 DefaultIterableDiffer,这两个类来检查对象和数组的变化。
这些类用于比较两个对象或两个数组之间的差异,从而决定是否需要更新视图。
3. 变更检测触发:
变更检测通常在事件处理程序、定时器回调、HTTP 请求完成等异步任务完成后自动触发。
也可以通过调用 detectChanges 方法手动触发变更检测。
Angular 的编译模式
JIT (Just-In-Time) 编译:在运行时编译模板。
AOT (Ahead-Of-Time) 编译:在构建时编译模板,提高了应用的启动速度。(默认编译方式)
由于应用程序是在浏览器内部运行之前进行编译的,因此浏览器会加载可执行代码并立即呈现应用程序,从而加快了呈现速度。
在AOT编译中,编译器将与应用程序一起发送外部HTML和CSS文件,从而消除了对那些源文件的单独AJAX请求,从而减少了ajax请求。
开发人员可以在构建阶段检测并处理错误,这有助于最大程度地减少错误。
AOT编译器将HTML和模板添加到JS文件中,然后再在浏览器中运行。 因此,没有多余的HTML文件可读取,从而为应用程序提供了更好的安全性。
更快的启动性能:因为大部分编译工作已经预先完成,所以用户在访问应用时不需要等待编译,这显著减少了初始加载时间。
更小的payload:AOT编译器能够消除未使用的代码和优化静态标记,从而减小最终的JavaScript文件大小。
更好的安全性:AOT编译有助于防止某些类型的注入攻击,因为它消除了对动态代码执行的需求。
更少的运行时错误:许多常见的模板错误在AOT编译期间就可以被检测出来,而不是在用户的设备上运行时才出现。
Angular 的双向绑定
- 与表单控件的双向绑定
- 从@angular/forms导入FormsModule
- 将 ngModel 指令与双向绑定语法(例如,
[(ngModel)] = "name")一起使用
- 组件之间的双向绑定
- 父组件必须
- 将model属性名称包装在双向绑定语法中。
- 将一个属性或一个信号赋给model属性
- 在子组件中使用 model 属性
- 父组件必须
通过脏数据检查(Dirty checking)来实现
Angular 的双向绑定是通过变更检测机制和 Zone.js 的结合来实现的。当数据模型发生变化时,Angular 会自动更新视图
Promise 和 Observable 的区别
首先新版本的anuglar是推荐使用 Observable (属于RxJS),其次,对于Observable对象,可以使用.toPromise()转化为Promise对象。
Promise,无论是否调用then。promise都会立即执行;而observables只是被创建,当调用(subscribe)的时候才会被执行。
Promise返回一个值;Observable返回0至N个值。所以Promise对应的操作符是.then(),Observable对应的是.subscribe
Observable,还额外支持 map,filter,reduce 和相似的操作符
Observable 可以取消,Promise不可以
Angular 中的 Zone.js
Zone.js 是一个 JavaScript 库,它为 JavaScript 的异步操作提供了统一的管理方式
1.异步操作拦截:
Zone.js 可以拦截 JavaScript 中的所有异步操作。
当异步操作开始时,Zone.js 会创建一个新的 Zone 并进入该 Zone。
当异步操作完成时,Zone.js 会退出当前 Zone 并返回到父 Zone。
2.变更检测触发:
在 Angular 中,每当一个异步操作完成时,Zone.js 会通知 Angular 执行变更检测。
这意味着 Angular 会在每个异步操作完成后检查数据模型是否有变化,并根据需要更新视图。
3.生命周期钩子调用:
Zone.js 还可以用来管理 Angular 组件的生命周期钩子方法,如 ngOnInit, ngOnDestroy 等。
当 Angular 组件初始化或销毁时,Zone.js 会确保这些生命周期钩子方法在正确的 Zone 内被调用。
4.错误处理:
Zone.js 可以帮助捕获和处理 JavaScript 中的错误,尤其是在异步操作中。
通过 Zone.js,可以更容易地追踪和调试错误。
Angular 性能优化
1.使用 OnPush 策略
OnPush 是另一种变化检测策略,它只在组件的输入属性发生变化时才执行变化检测。
当组件声明为 OnPush 模式时,Angular 只会在以下两种情况之一时才会运行变化检测:
a. 组件的输入属性发生了变化。
b. 组件的生命周期钩子被调用,或者有异步操作(如 HTTP 请求)完成。
这种策略大大减少了变化检测的工作量,因为只有受影响的组件及其直接子组件需要被检测。
2.避免不必要的更改检测
在不涉及任何数据绑定的函数中,避免使用 setTimeout()、setInterval() 或其他异步操作,因为这些操作会导致全局范围内的变化检测。
使用 ChangeDetectorRef.detach() 方法手动断开组件的变化检测连接,然后在适当的时候重新连接
3.使用 ChangeDetectionStrategy.OnPush 和 immutable 数据:
如果你的数据是不可变的(即你不会修改对象本身,而是创建一个新的副本来表示数据的更改),那么你可以结合 OnPush 策略来提高性能。
不可变的数据使得 Angular 可以通过简单的引用比较来判断数据是否发生了变化,而不是进行深度比较。
4.减少作用域内数据的数量:
减少组件内部作用域内的数据数量可以降低变化检测的时间复杂度。
避免在组件中使用大量的数组或对象,因为这会导致更多的数据比较。
5.利用 RxJS 操作符:
使用 RxJS 操作符,如 debounceTime()、switchMap() 等,可以控制何时触发变化检测。
懒加载组件:
对于不经常使用的组件,考虑使用路由懒加载,这样可以延迟加载这些组件,直到真正需要它们时再进行变化检测。
6.资源优化:
压缩和合并静态资源,如CSS和JavaScript文件。
使用CDN加速内容分发。
利用HTTP缓存和浏览器缓存。
Angular 中实现国际化
使用 @angular/localize
Angular CLI
ng new my-app
ng serve --open
Angular 实现文件上传预览
PDF 文件预览
npm i ng2-pdf-viewer -S
import { PdfViewerModule } from 'ng2-pdf-viewer';
.......................
handlePreview = async (file: NzUploadFile): Promise<void> => {
if (file.type.includes('application/pdf')) {
const name = file.response.data.files[0] ?? '';
this.pdfSrc = `/static/${name}`;
this.previewVisible = true;
console.log(this.pdfSrc);
}
}
..........................
// html
<pdf-viewer
[src]="pdfSrc"
[render-text]="true"
[original-size]="false"
style="width: 100%; height: 500px"
/>
Angular、Vue、React 的区别
1. 基本语法
判断:
Angular用 @if / *ngIf,Vue中使用v-if / v-show,React中使用jsx语法,三元运算符,&&符号...
属性绑定:
Angular用[], Vue中使用冒号(v-bind简写),React中使用 {}
事件绑定:
Angular用( ),Vue中用 @(v-on的简写),React使用 onClick
循环:
Angular用 @for(v15)/ *ngFor, Vue 使用 v-for,React 使用 map 方法
@for(item of array, track item.id){
{{item.name}}
}
<li *ngFor="let item of items">
{{ item }}
</li>
插槽:
Angular中用 <ng-content select='my-content' />
Vue中使用 <solt />
React中使用 props.children
2. 语法和模板
Vue.js使用了类似于HTML的模板语法,使得开发者可以声明性地定义UI。
Angular使用了 TypeScript,并使用了一种特殊的模板语言,模板中嵌入了指令和绑定。
React使用了 JSX(JavaScript XML),将UI组件的结构和逻辑组合在JavaScript代码中,这使得UI更接近JavaScript。
3. 状态管理
Vue.js提供了Vuex,一个用于状态管理的官方库,使得在大型应用中管理状态更容易。
Angular内置了RxJS,可用于处理应用程序的状态和异步操作。
React通常与Redux或Mobx等第三方状态管理库一起使用。
4. 数据请求
Angular中使用 HttpClient 模块,支持 Angular 的依赖注入,与 Angular 的其他特性如管道、RxJS 操作符等紧密集成,使得处理数据流更加容易, 默认支持 CORS 和 CSRF 保护,这在使用 Axios 时需要手动配置。
Vue、React则使用 axios
Angular 路由守卫和 Vue 路由守卫之间的差别
- 实现方式:
- Angular:路由守卫通常是一个实现了特定接口的类,需要在路由配置中注册。
- Vue:路由守卫通常是函数,可以直接在路由配置或组件内部定义。
- 类型:
- Angular:提供了
CanActivate、CanDeactivate、Resolve和CanLoad四种类型的守卫。 - Vue:提供了全局守卫(3)、路由独享守卫(3)和组件内守卫(1)三种类型的守卫。
- Angular:提供了
- 灵活性:
- Angular:守卫类的实现方式提供了更高的灵活性和复用性。
- Vue:守卫函数的实现方式更加简洁,适合快速开发。
- 应用场景:
- Angular:适合大型企业级应用,需要更细粒度的控制和复用。
- Vue:适合中小型项目,需要快速实现路由守卫功能。
Angular 中的 ng-content 和 Vue 中的插槽 以及 React 中的 props.children 之间的区别
Angular 中的 ng-content
<ng-content> 是 Angular 中的内容投影机制,允许你在组件模板中插入外部内容。
通过 <ng-content>,你可以将父组件中的内容传递到子组件中指定的位置。
属性:
可以使用 select 属性来指定投影内容的选择器,实现更细粒度的控制。
例如:<ng-content select=".header"></ng-content> 只会投影带有 .header 类的内容。
// 子组件
@Component({
selector: 'app-card',
template: `
<div class="card">
<ng-content select=".header"></ng-content>
<ng-content select=".body"></ng-content>
<ng-content select=".footer"></ng-content>
</div>
`
})
export class CardComponent {}
<!-- 父组件 -->
<app-card>
<div class="header">Header</div>
<div class="body">Body</div>
<div class="footer">Footer</div>
</app-card>
Vue 的插槽
基本功能:
Vue 的插槽(slots)也用于内容投影,但提供了更多的灵活性和功能。
支持默认插槽、具名插槽和作用域插槽。
1.默认插槽:不需要指定名称
<!-- 子组件 -->
<template>
<div class="card">
<slot></slot>
</div>
</template>
<!-- 父组件 -->
<app-card>
Default content
</app-card>
2.具名插槽: 允许你定义多个插槽,并通过 name 属性区分
<!-- 子组件 -->
<template>
<div class="card">
<slot name="header"></slot>
<slot name="body"></slot>
<slot name="footer"></slot>
</div>
</template>
<!-- 父组件 -->
<app-card>
<template v-slot:header>Header</template>
<template v-slot:body>Body</template>
<template v-slot:footer>Footer</template>
</app-card>
3.作用域插槽:允许子组件向父组件传递数据
<!-- 子组件 -->
<template>
<div class="card">
<slot :item="item"></slot>
</div>
</template>
<script>
export default {
data() {
return {
item: { name: 'Example' }
};
}
};
</script>
<!-- 父组件 -->
<app-card v-slot:default="slotProps">
{{ slotProps.item.name }}
</app-card>
React 的 props.children
基本功能:
props.children 是 React 中传递子组件内容的标准方式。
它可以是任何有效的 React 节点,包括文本、组件、数组等。
// 子组件
const Card = ({ children }) => (
<div className="card">
{children}
</div>
);
// 父组件
<Card>
<div>Header</div>
<div>Body</div>
<div>Footer</div>
</Card>
对比总结
功能丰富性:
Vue 的插槽功能最为丰富,支持默认插槽、具名插槽和作用域插槽,提供了高度的灵活性。
React 的 props.children 简单直接,适用于大多数场景,但缺乏 Vue 那样的细粒度控制。
Angular 的 <ng-content> 功能介于两者之间,支持选择器,但不如 Vue 的插槽灵活。
使用场景:
如果你需要复杂的、多样的内容投影,Vue 的插槽可能是最佳选择。
如果你只需要简单的内容传递,React 的 props.children 和 Angular 的 <ng-content> 都能满足需求。
Angular 版本迭代
- Angular 近年的迭代核心方向是:简化架构(从 NgModule 到独立组件)、提升响应式能力(信号系统)、优化构建性能(从 Webpack 到 Vite)、强化服务端渲染(SSR/SSG),同时紧跟 TypeScript 和现代 Web 标准,持续降低开发复杂度并提升应用性能
宿主元素
- 对于于每个与组件选择器匹配的 HTML 元素,Angular 都会创建该组件的一个实例。与组件选择器匹配的 DOM 元素就是该组件的宿主元素。组件模板的内容会在其宿主元素内部渲染