Angular Basic Knowledge Points

Angular

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          # 应用入口HTMLAngular会注入编译后的代码│   ├── 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 以便其他模块可以使用

基本语法

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, injectTemplateRefViewContainerRef } 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 = injectMyService  // 模板引用,用于访问模板。
  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()

  作用在组件的内容通过 contentChildcontentChildren | @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 有三个方法nexterror completenext 方法接收 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文件可读取从而为应用程序提供了更好的安全性
  更快的启动性能因为大部分编译工作已经预先完成所以用户在访问应用时不需要等待编译这显著减少了初始加载时间
  更小的payloadAOT编译器能够消除未使用的代码和优化静态标记从而减小最终的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 / *ngIfVue中使用v-if / v-showReact中使用jsx语法三元运算符&&符号...

属性绑定
  Angular用[], Vue中使用冒号v-bind简写),React中使用 {}

事件绑定
  Angular用( ),Vue中用 @(v-on的简写),React使用 onClick

循环
  Angular用 @forv15/ *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使用了 JSXJavaScript XML),将UI组件的结构和逻辑组合在JavaScript代码中这使得UI更接近JavaScript
3. 状态管理

  Vue.js提供了Vuex一个用于状态管理的官方库使得在大型应用中管理状态更容易
  Angular内置了RxJS可用于处理应用程序的状态和异步操作
  React通常与Redux或Mobx等第三方状态管理库一起使用
4. 数据请求

  Angular中使用 HttpClient 模块支持 Angular 的依赖注入, Angular 的其他特性如管道RxJS 操作符等紧密集成使得处理数据流更加容易, 默认支持 CORS CSRF 保护这在使用 Axios 时需要手动配置
  VueReact则使用 axios

Angular 路由守卫和 Vue 路由守卫之间的差别

  1. 实现方式
    • Angular:路由守卫通常是一个实现了特定接口的类,需要在路由配置中注册。
    • Vue:路由守卫通常是函数,可以直接在路由配置或组件内部定义。
  2. 类型
    • Angular:提供了 CanActivateCanDeactivateResolveCanLoad 四种类型的守卫。
    • Vue:提供了全局守卫(3)、路由独享守卫(3)和组件内守卫(1)三种类型的守卫。
  3. 灵活性
    • Angular:守卫类的实现方式提供了更高的灵活性和复用性。
    • Vue:守卫函数的实现方式更加简洁,适合快速开发。
  4. 应用场景
    • 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 元素就是该组件的宿主元素。组件模板的内容会在其宿主元素内部渲染