Angular
angular的依赖注入和控制反转
依赖注入(DI):
- 一个组件需要使用另一个服务或组件时,不需要自己创建或管理这个依赖。
- @Injectable()来标记可注入的服务,并通过构造函数中引入。
- 解决了组件之间的耦合问题。
控制反转(IoC):
- Angular 的 依赖注入 系统就是一个 IoC 容器,管理各种服务的创建和注入。
- 提高代码的灵活性和可测试性
@Injectable() 的providedIn使用方式
- providedIn: 'root': HTTP 服务、身份验证服务
- 默认方式,单列,应用程序级别。
- 当应用程序启动时,自动创建,整个程序中共享使用。
 
- providedIn: 'platform':  日志服务、配置服务
- 平台级别,单列,应用程序中共享。
 
- providedIn: 'any': 表单服务、对话框服务
- 可以在任何地方被注入,但不是单例。
- 每次注入时,会创建一个新的服务实例。
 
- providedIn: 'module': 路由服务、数据服务
- 在指定的 Angular 模块中可用。
- 当该模块被加载时,Angular 会创建该服务的单例实例。
 
- 自定义提供方:
- 除了以上四种内置的提供方式,你还可以自定义服务的提供方式。
- 可以通过 @Injectable({provider: ...})的方式来指定自定义的提供方。
- 这种方式通常用于更复杂的依赖注入场景,如动态注册服务、延迟加载服务等。
 
怎么实现依赖注入
提供依赖(Provide Dependencies):
- 使用 @Injectable()装饰器标记服务类,并指定其提供方式。
import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root' // 在根模块中提供
})
export class MyService {
  // 服务类的实现
}
注入依赖(Inject Dependencies):
- 构造函数参数的方式注入依赖。
import { Component } from '@angular/core';
import { MyService } from './my.service';
@Component({
  selector: 'app-my-component',
  templateUrl: './my.component.html'
})
export class MyComponent {
  constructor(private myService: MyService) {
    // 在组件中使用 myService
  }
}
注入令牌(Injection Tokens):
- 对于非类型的依赖,可以使用注入令牌(InjectionToken)进行注入。
import { InjectionToken } from '@angular/core';
export const CONFIG_OPTIONS = new InjectionToken<any>('config options');
@Injectable({
  providedIn: 'root',
  useFactory: () => ({
    apiUrl: 'https://api.example.com'
  }),
  deps: [CONFIG_OPTIONS]
})
export class MyService {
  constructor(@Inject(CONFIG_OPTIONS) private config: any) {}
}
自定义提供者
- 
useValue: - 
这种提供者用于提供一个固定的值作为服务。 
- 
通常用于提供应用程序配置、常量等。 @Injectable({
 providedIn: 'root',
 providers: [
 { provide: 'API_URL', useValue: 'https://api.example.com' }
 ]
 })
 class MyService {
 constructor(@Inject('API_URL') private apiUrl: string) {}
 }
 
- 
- 
useFactory: - 
这种提供者用于提供一个工厂函数,该函数返回服务实例。 
- 
通常用于根据某些条件或依赖来动态创建服务实例。 @Injectable({
 providedIn: 'root',
 providers: [
 {
 provide: 'LoggerService',
 useFactory: (config: AppConfig) => {
 return config.logLevel === 'debug' ? new DebugLoggerService() : new ProductionLoggerService();
 },
 deps: [AppConfig]
 }
 ]
 })
 class MyService {
 constructor(@Inject('LoggerService') private logger: LoggerService) {}
 }
 
- 
- 
useExisting: - 
这种提供者用于将一个服务重新映射到另一个服务。 
- 
通常用于为现有服务提供别名或包装。 @Injectable({
 providedIn: 'root',
 providers: [
 { provide: 'HttpClient', useExisting: HttpClient }
 ]
 })
 class MyService {
 constructor(@Inject('HttpClient') private http: HttpClient) {}
 }
 
- 
- 
useClass: - 
这种提供者用于提供一个具体的类作为服务。 
- 
通常用于根据条件提供不同的服务实现。 @Injectable({
 providedIn: 'root',
 providers: [
 { provide: 'LoggerService', useClass: config.logLevel === 'debug' ? DebugLoggerService : ProductionLoggerService }
 ]
 })
 class MyService {
 constructor(@Inject('LoggerService') private logger: LoggerService) {}
 }
 
- 
@Inject
注入自定义令牌或可选依赖
管理组件和服务之间的依赖关系
指令和component区别
- 视图和模板:
- 组件有自己的视图和模板,由HTML、CSS、TypeScript组成。
- 指令通常不具有自己的视图和模板,而是修改或扩展现有的 DOM 元素。
 
- 模板引用
- 
组件: 使用模板引用变量来引用自身的 HTML 元素。 
- 
指令通常不需要使用模板引用变量。 <some-element #varName></some-element>
 @ViewChild('varName') myElementRef: ElementRef;
测试
- 组件 :使用单元和集成测试,确保功能正确。
- 指令: 使用单元测试,测试的重点是对 DOM 的修改和行为。
$event
- 绑定事件, 自动传递到组件中的处理方法中。
- 会根据事件类型的不同而有所不同,比如鼠标/键盘事件、表单事件或自定义事件。
angular中组件之间通信的方式
- Input 属性: 父子组件使用[],子组件使用@Input接收并使用这些数据。
- Output 事件:
- 子使用@Output + EventEmitter.emit()父组件发送事件。
- 父组件使用() + $event绑定事件,获取数据。
 
- 子使用
- 服务(service): 注入同一个服务在组件之间传递数据。
- 路由参数: 导航时设置参数, 目标组件中使用ActivatedRoute,.params.subscribe获取。
- 本地存储:
- 使用浏览器的本地存储(如 localStorage、sessionStorage)在组件之间共享数据。
 
- RxJS Observables:
- 父组件创建Subjectasync管道订阅将值显示在模板。.next()向子组件发送数据。
 
- 父组件创建
- @ViewChild 和 @ViewChildren 子组件或 DOM 元素的引用
- 依赖注入: 使用依赖注入在组件之间共享数据或服务。
Observable 在 Angular 中有几种常见的类型:
订阅者订阅相关主题,发布者发布主题事件,通知订阅该主题的对象
- 基本 Observables:
- Observable: 创建和订阅自定义的数据流。
- Subject: 作为数据的生产者和消费者。可以被多个订阅者订阅,可以手动发送事件。
 
- RxJS Observables:
- BehaviorSubject: 记录最新发送的值,有新的订阅者立即发送。
- ReplaySubject: 记录最近发送的多个值,有新的订阅者立即发送。
- AsyncSubject: 会在 Observable 完成时发送最后一个值。
 
- HTTP Observables:
- HttpClient: 返回的是 Observable 类型。
 
- Router Observables:
- Router:- events、- routerState,用于监听路由相关的事件和状态变化。
 
- Form Observables:
- FormControl: 表单控件,它提供了- valueChanges和- statusChanges这两个 Observable 属性,用于监听表单控件的值和状态变化。
- FormGroup:- valueChanges 值发生变化时发出通知
- statusChanges 状态发生变化时发出通知 'VALID'、'INVALID'、'PENDING'或'DISABLED
- get() 获取表单组中指定的表单控件
- controls 返回一个包含表单组中所有表单控件的对象
 
 
- Other Observables:
- AnimationEvent: 动画事件的 Observable。
- ResizeEvent: 窗口大小变化事件的 Observable。
- 以及其他一些 Angular 服务和指令提供的 Observable 属性。
 
数据绑定的方式有哪些
- 插值绑定 (Interpolation): 使用 {{expression}}属性值插入到模板中。
- 管道绑定 (Pipe Binding): 使用 {{expression | pipe}}将组件数据通过管道进行转换后显示在模板中。
- 属性绑定 (Property Binding): 使用 [property]="expression"属性绑定到模板元素的属性上。
- 指令绑定 (Directive Binding): 使用 [myDirective]="expression"将组件数据绑定到自定义指令的属性上。
- 事件绑定 (Event Binding): 使用 (event)="expression"事件绑定到模板元素的事件上。
- 双向绑定 (Two-way Binding): 使用 [(ngModel)]="expression"实现组件属性和表单元素之间的双向数据绑定。
- 模板引用变量 (Template Reference Variables): 使用 #varName在模板中定义一个引用变量,可以访问对应的 DOM 元素或组件实例。
angular的八大组成部分
- 模块(Module):  组织和打包应用程序的不同部分。 用 @NgModule定义。
- 组件(Component): 视图层。用 @Component定义组件
- 模板(Template): 定义组件的 HTML 结构。用模板语法来动态渲染视图。
- 指令(Directive): 操作 DOM 元素或组件。 用 @Directive定义。
- 服务(Service): 封装通用逻辑。@Injectable定义。
- 依赖注入(DI): 管理组件和服务之间的依赖关系。使用 @Injectable定义。 通过构造函数注入依赖项。
- 路由(Router):  管理应用程序的导航和页面切换。  @Router配置路由。
- 表单(Forms): 管理表单输入和验证,模版板驱动表单和响应式表单。
模版板驱动表单和响应式表单
模板驱动表单 (Template-Driven Forms):
- 
特点: - 
定义和验证在模板中完成,只需在模板中添加相关指令即可。 
- 
NgModel状态和验证信息。
- 
适合于简单的表单 
- 
测试和调试较困难,因为表单逻辑耦合在模板中。 
- 
不适合动态表单或复杂的验证逻辑。 
 
- 
响应式表单 (Reactive Forms):
- 特点:
- 定义和验证在组件类中完成,使用 FormGroup、FormControl等类。
- 表单状态和验证信息存储在 FormGroup和FormControl实例中。
- 适合动态交互和复杂验证
- 更易于测试和调试,逻辑集中在组件类中。
 
- 定义和验证在组件类中完成,使用 
常用的生命周期钩子函数
- 组件创建阶段:
- constructor(): 依赖注入和初始化属性。
- ngOnChanges(changes: SimpleChanges): Input 属性发生变化时被调用。
- ngOnInit(): 初始化组件的逻辑。
- ngDoCheck(): 变更检测。可以自定义的变更检测逻辑。
- ngAfterContentInit(): 访问和操作组件的内容。
- ngAfterContentChecked(): 对组件内容的额外检查。
- ngAfterViewInit(): 访问和操作组件的视图及其子视图。
- ngAfterViewChecked(): 执行对组件视图的额外检查。
 
- 组件更新阶段:
- ngOnChanges(changes: SimpleChanges): 并且会在每次属性变化时被调用。
- ngDoCheck(): 在每次变更检测运行时被调用。可以在这里实现自定义的变更检测逻辑。
- ngAfterContentChecked(): 执行对组件投影内容的额外检查。
- ngAfterViewChecked(): 执行对组件视图的额外检查。
 
- 组件卸载阶段:
- ngOnDestroy(): 在组件销毁前被调用。可以在这里释放资源,如取消订阅、清理定时器等。
 
·@Component可选参数
- selector:
- 生命组件名字,在模板中引用。
 
- templateUrl 或 template:
- templateUrl引入外部文件。
- template内嵌模版。
 
- styleUrls 或 styles:
- styleUrls指定组件样式的外部文件路径列表。
- styles内嵌组件样式。
 
- encapsulation:
- 定义组件样式的封装策略,可选值为 ViewEncapsulation.Emulated、ViewEncapsulation.Native或ViewEncapsulation.None。
 
- 定义组件样式的封装策略,可选值为 
- changeDetection:
- 定义组件的变更检测策略,可选值为 ChangeDetectionStrategy.Default或ChangeDetectionStrategy.OnPush。
 
- 定义组件的变更检测策略,可选值为 
- providers:
- 引入所需的服务。
 
- viewProviders:
- 引入视图所需的服务。
 
- animations:
- 定义动画。
 
- host:
- 定义组件在宿主元素上的属性和事件绑定。
 
- exportAs:
- 定义组件在模板中的导出名称。
 
- inputs 和 outputs:
- 定义组件的输入属性和输出事件。
 
- moduleId:
- 指定组件所在模块的 ID,用于解析相对路径。
 
路由的工作原理
- 路由配置:
- 在 Angular 应用程序中,开发者需要先定义路由配置,指定每个 URL 对应的组件。
- 路由配置通常放在单独的模块中,如 app-routing.module.ts。
- 在配置中,使用 RouterModule.forRoot()或RouterModule.forChild()来注册路由。
 
- 路由激活: 输入 URL 或点击链接时,路由器会监听 URL 的变化 进行匹配,找到对应的组件。
- 组件渲染: 将该组件渲染到<router-outlet>所在的位置。 路由出口通常位于应用程序的主要布局组件中,如app.component.html。
- 导航: 点击链接或 router.navigate()来触发导航。路由器的 URL 监听和匹配过程。
- 路由参数: 定义参数化的 URL,如 /users/:id。 匹配成功后,路由器会将参数值注入到对应组件的ActivatedRoute服务中,供组件使用。
- 导航守卫:
- Angular 提供了一系列的导航守卫,如 CanActivate、CanDeactivate等,用于控制导航的行为。
- 开发者可以自定义导航守卫,在导航发生时执行相应的逻辑,如权限验证、数据预加载等。
 
- Angular 提供了一系列的导航守卫,如 
- 路由状态管理:
- Angular 路由器会维护当前路由的状态,包括 URL、参数、查询参数等。
- 开发者可以通过 ActivatedRoute服务访问和订阅路由状态的变化。
 
rxjx在angular中的使用场景
- 异步数据流管理:Angular 中大量使用 RxJS 来处理异步数据
- 状态管理: 如表单状态、加载状态、错误状态等。
- 通过 BehaviorSubject、ReplaySubject等 RxJS 主体,可以在组件间共享和订阅状态变化。
 
- 通过 
- 表单处理:
- Angular 的响应式表单(ReactiveFormsModule)底层就是基于 RxJS 实现的。
- 开发者可以利用 RxJS 操作符来实现表单的各种验证、联动等复杂逻辑。
 
- Angular 的响应式表单(
- 事件处理:
- Angular 事件绑定((event))本质上也是 RxJS 事件流。
- 开发者可以使用 RxJS 操作符来处理事件,如防抖、节流、组合多个事件等。
 
- Angular 事件绑定(
- 路由管理:
- Angular 路由器内部广泮使用 RxJS,如路由参数的订阅、导航守卫的实现等。
- 开发者可以利用 RxJS 来管理路由状态,实现复杂的导航逻辑。
 
- 测试:
- RxJS 提供了丰富的测试工具,如 TestScheduler、marble testing等,有助于 Angular 应用程序的单元测试和集成测试。
 
- RxJS 提供了丰富的测试工具,如 
管道的作用
在模板中直接显示格式化的数据,无需进行繁琐的数据处理逻辑。
- 数据转换:
- 过滤和排序:
- 国际化和本地化:
- 根据用户的语言环境自动选择合适的格式化方式。
 
- 复杂逻辑封装:
- 封装一些复杂的逻辑,如计算、组合等。
- 将复杂的逻辑从组件中抽离出来。
 
- 可测试性:
- 管道是纯函数,输入相同就会得到相同的输出,这使得管道更加易于测试。
- 通过管道,可以将一些复杂的逻辑从组件中分离出来,从而提高组件的可测试性。
 
双向绑定的原理
结合模板语法和表单相关的指令来实现的
- 双向绑定的实现:
- 使用 [(ngModel)]指令来实现双向绑定。内部集成了模型到视图和视图到模型的绑定逻辑。
 
- 使用 
- 模型到视图的绑定:
- 数据变化时,脏检查机制会检测到。然后自动更新绑定到该数据模型的视图。
 
- 视图到模型的绑定:
- 用户进行交互操作时,捕获这些事件。然后根据事件类型和绑定关系,自动更新对应的数据模型。
 
兄弟组件之间可能会存在一些间接的生命周期联系
- 父组件的影响:
- 当父组件的 ngOnInit被调用时,它可能会触发子组件的ngOnInit。
 
- 当父组件的 
- 数据变化的影响:
- 当一个组件更新了共享数据时,可能会触发另一个组件的 ngOnChanges钩子。
 
- 当一个组件更新了共享数据时,可能会触发另一个组件的 
- 事件传递的影响:
- 当一个组件触发了一个事件,另一个组件的事件处理器可能会执行某些操作,从而影响到该组件的生命周期。
 
keep alive生命周期
- 组件创建: 当 *ngIf或*ngFor条件为true时,组件会正常创建并执行生命周期钩子,如ngOnInit、ngOnChanges等。
- 组件隐藏: 当 *ngIf或*ngFor条件为false时,组件不会被销毁,而是进入"隐藏"状态。此时,组件会执行ngOnDestroy钩子,但会保持在内存中。
- 组件重新显示: 当 *ngIf或*ngFor条件再次为true时,组件不会被重新创建,而是从"隐藏"状态切换回"显示"状态。此时,组件不会执行ngOnInit等生命周期钩子,而是直接执行ngOnShow钩子。
- 组件销毁: 当组件最终被移除时(例如父组件被销毁),才会执行 ngOnDestroy钩子并彻底销毁该组件。
使用场景:
- 保持组件状态: 当组件需要保持一些内部状态时,使用 keepAlive可以避免组件被销毁和重新创建,从而保持状态不丢失。
- 性能优化: 对于一些复杂的组件,如果频繁创建和销毁会影响性能,使用 keepAlive可以减少组件的创建和销毁,提高性能。
- 延迟加载: 对于一些不常用的组件,使用 keepAlive可以实现延迟加载,提高应用程序的启动速度。
angular的diff算法的作用
diff 算法的工作原理:
- 首次在将模板渲染到 DOM 时,会首先将模板转换为虚拟 DOM 树。
- 当数据发生变化时,会重新生成一棵新的虚拟 DOM 树。
- 将新旧两棵虚拟 DOM 树进行比较,找出差异。
- 只会将发生变化的部分更新到实际的 DOM 中。
angular指令分类
- 结构型指令 (Structural Directives)
- 用于控制 DOM 结构的指令,例如 *ngIf、*ngFor、*ngSwitch等。
- 这些指令会根据条件动态地添加、移除或替换 DOM 元素。
 
- 用于控制 DOM 结构的指令,例如 
- 属性型指令 (Attribute Directives)
- 用于改变 DOM 元素的外观和行为的指令,例如 ngClass、ngStyle、ngModel等。
- 这些指令会修改元素的属性、样式或添加事件处理程序。
 
- 用于改变 DOM 元素的外观和行为的指令,例如 
- 组件 (Components)
- 组件是一种特殊的指令,它封装了 HTML 模板、CSS 样式和 TypeScript 逻辑。
- 组件通常用于创建可重用的 UI 元素,如页面、表单、按钮等。
 
- 自定义指令 (Custom Directives)
- 开发者可以根据业务需求创建自定义的指令。
- 自定义指令可以是结构型指令或属性型指令,用于实现特定的功能。
 
下面是一些常见的内置指令示例:
结构型指令:
- *ngIf: 根据条件显示或隐藏 DOM 元素
- *ngFor: 循环渲染列表数据
- *ngSwitch: 根据表达式的值切换不同的 DOM 元素
属性型指令:
- ngClass: 根据条件动态地添加或移除 CSS 类
- ngStyle: 根据条件动态地设置元素样式
- ngModel: 实现双向数据绑定
组件:
- app-header: 应用程序头部组件
- app-sidebar: 应用程序侧边栏组件
- app-product-list: 产品列表组件
angular双数组和指令的区别
- 双向数据绑定 (Two-Way Binding):
- 双向数据绑定是一种特殊的数据绑定机制,它可以在组件的属性和 UI 元素之间建立双向关联。
- 当组件的属性发生变化时,UI 元素会自动更新;当 UI 元素发生变化时,组件的属性也会自动更新。
- 通常使用 [(ngModel)]指令来实现双向数据绑定。
 
- 指令 (Directives):
- 指令是 Angular 中的一种特殊类型,用于扩展 HTML 元素的行为和功能。
- 指令分为三种类型: 结构型指令、属性型指令和组件。
- 指令可以用于操作 DOM 元素、添加事件处理程序、修改样式等。
 
区别总结:
- 目的: 双向数据绑定主要用于实现组件属性和 UI 元素之间的双向关联,而指令则用于扩展 HTML 元素的功能。
- 实现方式:
- 双向数据绑定使用 [(ngModel)]指令来实现。
- 指令可以是结构型、属性型或组件,需要通过编写指令类来实现。
 
- 双向数据绑定使用 
- 应用场景:
- 双向数据绑定常用于表单元素、输入框等需要实时更新数据的场景。
- 指令可以用于实现各种自定义的 DOM 操作和交互逻辑。
 
ng-class和ng-style区别
应用对象不同:
- ngClass字符串、对象或数组来指定,用于动态地添加或删除 CSS 类。
- ngStyle对象来指定 用于动态地设置元素的样式属性。
component和module区别
- 作用:
- 组件: 定义应用的视图和交互行为。
- 模块: 用于管理应用中相关的功能块。
 
- 服务和依赖注入:
- 组件: 通过依赖注入的方式使用服务。
- 模块: 可以定义应用级别的服务,并在模块中提供(provide)这些服务。
 
- 路由:
- 组件: 作为路由的目标组件。
- 模块: 定义路由配置,在模块中导入路由模块。
 
angular中你对服务有什么了解
- 定义:
- 服务是一个独立的、可复用的代码块,用于封装与应用程序逻辑相关的功能。
 
- 依赖注入:
- 通过依赖注入(Dependency Injection)的方式在组件或其他服务中使用。
 
- 生命周期管理:
- 服务的生命周期由 Angular 的依赖注入系统管理,服务实例可以是单例(Singleton)或多实例。
- 服务可以通过构造函数注入(Constructor Injection)或属性注入(Property Injection)的方式在组件中使用。
 
- 注册和提供:
- 服务需要在模块或组件的 providers数组中注册,才能被 Angular 的依赖注入系统识别和使用。
- 通常在应用级别的根模块中注册服务,以便在整个应用程序中使用。
 
- 服务需要在模块或组件的 
- 示例:
- 数据访问服务:用于与后端 API 进行交互,获取和更新数据。
- 身份验证服务:用于管理用户的登录状态和权限。
- 日志服务:用于记录应用程序的日志信息。
- 配置服务:用于管理应用程序的配置选项。
 
装饰器有哪些
- 类装饰器:
- @Component: 用于定义 Angular 组件,包含模板、样式等。
- @NgModule: 用于定义 Angular 模块,包含组件、指令、服务等。
- @Injectable: 用于定义一个可注入的服务。
 
- 属性装饰器:
- @Input: 用于标记组件的输入属性。
- @Output: 用于标记组件的输出属性(事件)。
- @HostBinding: 用于将宿主元素的属性绑定到组件实例的属性。
- @HostListener: 用于将宿主元素的事件绑定到组件的方法。
 
- 方法装饰器:
- @HostListener: 用于绑定宿主元素的事件到组件的方法。
- @ContentChild/- @ContentChildren: 用于获取组件的内容投影子元素。
- @ViewChild/- @ViewChildren: 用于获取组件的视图子元素。
 
- 参数装饰器:
- @Inject: 用于标记依赖注入的令牌。
- @Optional: 用于标记可选依赖注入。
- @Self: 用于标记从自身的提供者中查找依赖注入。
- @SkipSelf: 用于标记从父级提供者中查找依赖注入。
 
angular优缺点
优点:
- 完整的框架】: Angular 提供了一个完整的前端开发解决方案,包括模块化、数据绑定、路由、表单处理、依赖注入等多个方面,开发者无需自行搭建这些基础设施。
- TypeScript 支持: Angular 使用 TypeScript 作为主要语言,TypeScript 提供了更好的类型检查和 IDE 支持,有助于编写更可靠和可维护的代码。
- 组件化开发: Angular 采用组件化的开发模式,可以更好地实现代码的复用和模块化。
- 优秀的性能: Angular 通过变更检测机制和虚拟 DOM 等技术,可以实现高性能的页面渲染。
- 强大的CLI工具: Angular CLI 提供了丰富的命令行工具,可以快速生成组件、服务、模块等,大幅提高开发效率。
- 良好的可测试性: Angular 的设计考虑了可测试性,使用依赖注入、观察者模式等模式,有助于编写可测试的代码。
- 活跃的社区: Angular 拥有一个活跃的开发者社区,提供丰富的第三方库和工具。
缺点:
- 学习曲线较陡: Angular 作为一个全面的框架,其概念和使用方式相对较为复杂,需要一定的学习成本。
- bundle 体积较大: Angular 的整体体积较大,特别是在生产环境中需要对代码进行优化。
- 单页应用导向: Angular 更适合开发单页应用(SPA),对于不需要完整 SPA 功能的项目可能有些过重。
- 与React生态差异较大: Angular 与React及其生态系统在开发哲学和使用方式上有较大差异,对于同时使用两者的开发者来说需要进行较多切换。
- 升级成本高: Angular 的主要版本升级通常需要进行较大的改动,升级成本较高。
- 性能问题: 在某些场景下,如大量数据展示,Angular 的性能可能无法达到React或Vue的水平。
执行变更检测的情况
组件状态何时发生更新
- 用户交互事件:
- 当用户触发事件(如点击、输入等)时,Angular 会执行变更检测,更新受影响的组件。
 
- Observable 数据变化:
- 当组件订阅的 Observable 数据发生变化时,Angular 会执行变更检测。
 
- 定时器:
- 当组件内部使用定时器(如 setTimeout、setInterval)时,Angular 会在定时器触发时执行变更检测。
 
- Promise 完成:
- 当组件内部使用 Promise 时,Promise 完成后 Angular 会执行变更检测。
 
- HTTP 请求完成:
- 当组件发起 HTTP 请求时,请求完成后 Angular 会执行变更检测。
 
- 手动触发:
- 开发者可以手动调用 ChangeDetectorRef服务来触发变更检测。
 
- 开发者可以手动调用 
- NgZone 事件:
- 当组件位于 NgZone 内部时,某些 NgZone 事件(如 setTimeout、event handlers 等)会触发变更检测。
 
- 路由导航:
- 在路由导航期间,Angular 会执行变更检测来确保页面正确渲染。
 
- 生命周期钩子:
- 在组件的生命周期钩子被调用时,Angular 会执行变更检测。
 
脏检查机制
负责跟踪和更新数据模型的变化。
它依赖于 Zone.js 拦截事件,并在事件循环的最后阶段进行检查。
通过选择合适的变化检测策略和使用优化技术,开发者可以进一步提高脏检查的性能。
zone是什么
提供了一种机制来拦截和追踪异步操作
异步任务跟踪:
- 跟踪应用程序中的所有异步任务,就可以知道何时需要执行变更检测来更新视图。
执行上下文管理:
- 将应用程序的执行环境划分为不同的区域。可以针对不同的区域执行不同的变更检测策略。
错误处理:
- 可以捕获应用程序中发生的异步错误,报告给错误处理机制。帮助开发者更好地调试和诊断应用程序的问题。
AOT和JIT
Angular 提供了两种编译模式:AOT(Ahead-of-Time)和JIT(Just-in-Time)。
- AOT 编译:
- AOT 编译是在构建时完成的。
- 将模板和组件代码预先编译为 JavaScript 代码,生成优化后的应用程序包。
- 还可以提前发现一些编译错误,通常在生产环境中使用。
 
- JIT 编译:
- JIT 编译是在应用程序运行时完成的。
- 它会在浏览器中动态编译 Angular 模板和组件代码。
 
router和route
- Router:
- 管理程序的导航和路由。
- 用于定义路由、监听路由变化操作。
- 需要在程序的入口模块(通常是 AppModule)中配置 服务。
 
- Route:
- 定义路由的基本单元。
- 是一个对象包含了一个路径和一个组件的映射关系。
- 应用程序中定义多个 Route 对象,形成一个路由配置。
- 当用户访问某个路径时,会根据配置的 Route 匹配并渲染相应的组件。
 
// 在 AppRoutingModule 中定义路由配置
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}
// 在 AppComponent 中使用 Router 进行导航
<nav>
  <a routerLink="/" routerLinkActive="active">Home</a>
  <a routerLink="/about" routerLinkActive="active">About</a>
  <a routerLink="/contact" routerLinkActive="active">Contact</a>
</nav>
<router-outlet></router-outlet>
Router 负责管理应用程序的路由,而 Route 则定义了具体的路由映射关系
mvvm和mvc区别
- 组件职责分工:
- MVC 模式中,Model 负责数据和业务逻辑,View 负责界面显示,Controller 负责处理用户输入并更新 Model 和 View。
- MVVM 模式中,Model 同样数据和业务逻辑,View 负责界面显示,ViewModel 负责处理 Model 和 View 之间的通信。
 
- 视图和控制器的关系:
- MVC 中,View 和 Controller 是紧耦合的,Controller 直接操作 View 的渲染。
- MVVM 中,ViewModel 与 View 是松耦合的,ViewModel 通过数据绑定与 View 进行交互,View 只负责渲染。
 
- 数据流向:
- MVC 中,数据流向是单向的
- MVVM 中,数据流向是双向的:
 
- 可测试性:
- MVC 中,Controller 通常很难测试,因为它同时依赖于 Model 和 View。
- MVVM 中,ViewModel 可以独立于 View 进行测试,因为它不依赖于 View 的具体实现。
 
angular性能优化
- Tree-Shaking:
- 利用 Rollup 等打包工具移除未使用的代码,减少应用体积。
- 在 Angular CLI 中,默认会自动进行 Tree-Shaking。
 
- AOT (Ahead-of-Time) 编译:
- AOT 编译可以在应用构建时将模板预编译,减少运行时的编译开销。
- 在 Angular CLI 中,可以通过设置 aot: true来开启 AOT 编译。
 
- 懒加载 (Lazy Loading): route 中 loadChildren
- 将应用拆分成多个模块,按需加载模块,减少初始加载时间。
- 在 Angular 中,可以通过路由配置实现模块的懒加载。
 
- 变更检测优化:
- 使用 OnPush 变更检测策略来减少不必要的变更检测。
- 使用 trackBy 函数优化 *ngFor 循环的性能。
 
- 分离容器和展示组件:
- 将业务逻辑与展示逻辑分离,提高组件的可重用性。
- 容器组件负责数据获取和业务逻辑,展示组件负责渲染。
 
- 使用 RxJS 优化:
- 合理使用 RxJS 操作符如 debounceTime、distinctUntilChanged 等来优化订阅和数据流。
- 使用 OnPush 变更检测策略配合 Observables 可以进一步优化性能。
 
- 优化第三方库的使用:
- 仅引入需要的第三方库,避免引入过多无用代码。
- 使用 Angular 的惰性加载特性,按需加载第三方库。
 
- 代码分割:
- 利用 Angular 的 code splitting 特性,将代码分割成多个 chunk,按需加载。
- 通过路由配置实现基于路由的代码分割。
 
- Web Worker 和 Service Worker:
- 将部分计算密集型任务迁移到 Web Worker 中,提高主线程响应速度。
- 使用 Service Worker 实现离线缓存和网络优化。
 
- 性能监控和分析:
- 使用 Angular 内置的性能监控工具 ng.probe($0)分析性能问题。
- 结合第三方工具如 Chrome DevTools 进行性能分析和优化。
 
- 使用 Angular 内置的性能监控工具 
- 管道优化
angular热更新
- 
安装 HMR 相关依赖: - 安装 webpack-dev-server和@angularclass/hmr等依赖包。
 
- 安装 
- 
配置 HMR 环境: - 在 angular.json文件中,将serve命令的options配置项中的hmr属性设置为true。
- 在 src/main.ts文件中,导入hmrBootstrap函数并替换原有的platformBrowserDynamic().bootstrapModule()调用。
 import { hmrBootstrap } from './hmr';
 // 替换为
 hmrBootstrap(AppModule, bootstrap);
- 在 
- 
编写 HMR 模块: - 在 src/hmr.ts文件中定义hmrBootstrap函数,用于在热更新时重新渲染应用。
 import { NgModuleRef, ApplicationRef } from '@angular/core';
 import { createNewHosts } from '@angularclass/hmr';
 export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
 let ngModuleRef: NgModuleRef<any>;
 bootstrap().then(moduleRef => (ngModuleRef = moduleRef));
 module.hot.accept();
 module.hot.dispose(() => {
 const appRef: ApplicationRef = ngModuleRef.injector.get(ApplicationRef);
 const elements = appRef.components.map(c => c.location.nativeElement);
 const makeVisible = createNewHosts(elements);
 ngModuleRef.destroy();
 makeVisible();
 });
 };
- 在 
- 
启用热更新: - 在开发环境下,运行 ng serve --hmr命令启用热更新功能。
 
- 在开发环境下,运行 
anuglar服务端渲染
angular Universal 实现这一功能
- 
安装 Angular Universal 依赖: - 运行 ng add @nguniversal/express-engine命令安装 Angular Universal 所需的依赖包。
 
- 运行 
- 
配置服务端渲染: - 在 app.server.module.ts文件中, 配置服务端渲染所需的模块和组件。
- 在 server.ts文件中, 编写 Express 服务器的逻辑,用于处理服务端渲染的请求。
 // server.ts
 import 'zone.js/dist/zone-node';
 import { ngExpressEngine } from '@nguniversal/express-engine';
 import * as express from 'express';
 import { join } from 'path';
 import { AppServerModule } from './src/main.server';
 import { APP_BASE_HREF } from '@angular/common';
 import { existsSync } from 'fs';
 // Express server
 const server = express();
 const distFolder = join(process.cwd(), 'dist/my-app/browser');
 const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
 // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
 server.engine('html', ngExpressEngine({
 bootstrap: AppServerModule,
 }));
 server.set('view engine', 'html');
 server.set('views', distFolder);
 // Example Express Rest API endpoints
 // app.get('/api/**', (req, res) => { });
 // Serve static files from /browser
 server.get('*.*', express.static(distFolder, {
 maxAge: '1y'
 }));
 // All regular routes use the Universal engine
 server.get('*', (req, res) => {
 res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
 });
 // Start up the Node server
 server.listen(process.env.PORT || 4000, () => {
 console.log(`Node server listening on http://localhost:${process.env.PORT || 4000}`);
 });
- 在 
- 
构建和运行服务端渲染应用: - 运行 ng build --prod命令构建应用。
- 运行 node dist/my-app/server/main.js命令启动服务器,提供服务端渲染。
 
- 运行 
使用 Angular Universal 进行服务端渲染的主要好处包括:
- 更快的初始加载速度: 服务端渲染可以生成静态 HTML 页面,减少客户端的初始渲染时间。
- 更好的搜索引擎优化: 搜索引擎爬虫可以更好地抓取服务端渲染的页面内容。
- 更好的用户体验: 初次访问时,用户可以立即看到页面内容,提高了感知性能。
Jasmine编写注意事项
- 测试结构:
- 使用 describe()函数定义测试套件,表示测试的主要功能或场景。
- 使用 it()函数定义具体的测试用例,描述预期的行为。
- 使用 beforeEach()和afterEach()函数定义测试用例的初始化和清理操作。
 
- 使用 
- 断言:
- 使用 expect()函数来编写断言,验证测试结果是否符合预期。
- Jasmine 提供了丰富的断言 API,如 toBe()、toEqual()、toContain()、toBeTruthy()等。
- 对于异步操作,可以使用 async/await或done回调来处理异步断言。
 
- 使用 
- 依赖注入:
- 使用 TestBed来创建测试环境,配置依赖关系并注入服务。
- 可以使用 providers配置项注入服务,或使用inject()函数动态注入。
- 对于需要模拟的服务,可以使用 spyOn()函数来设置预期的行为。
 
- 使用 
- 组件测试:
- 使用 TestBed.createComponent()创建组件的测试环境。
- 通过 fixture.detectChanges()触发组件的变更检测,确保测试时组件状态更新。
- 可以访问组件实例 fixture.componentInstance和模板元素fixture.nativeElement进行断言。
 
- 使用 
- 覆盖率:
- 使用 karma-coverage插件生成代码覆盖率报告。
- 在 karma.conf.js中配置覆盖率报告的输出目录和格式。
- 可以设置覆盖率阈值,作为持续集成的一部分。
 
- 使用 
- 其他:
- 使用 spyOn()和jasmine.createSpy()来模拟外部依赖。
- 对于涉及 DOM 操作的组件,可以使用 TestBed.overrideTemplate()来替换模板。
- 在测试中使用 console.log()或debugger语句来调试问题。
 
- 使用 
jasmine和Karma
Jasmine:
- 测试框架,用于编写和运行单元测试。
- 提供了一套丰富的 API,用于定义测试套件、断言、钩子函数等。
describe('MyComponent', () => {
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should have a title', () => {
    expect(component.title).toBe('My Component');
  });
});
Karma:
- 测试运行器, 自动化运行测试用例。
- 可以在多种浏览器和环境中运行测试用例,
- 可以自定义测试运行的行为,如监听文件变化、生成覆盖率报告等。
Angular 生态系统中
- Angular Universal [ˌjunɪˈvɜrsl]:
- 服务端渲染 (Server-Side Rendering, SSR) 解决方案。
- 它可以在服务端渲染 Angular 应用程序,并将渲染好的 HTML 返回给客户端,提高首屏加载速度和 SEO 友好性。
 
- Nx:
- Nx 是一个由 Nrwl 公司开发的 Angular 应用程序构建和开发框架。
- Nx 提供了一个基于 Webpack 的构建系统,支持代码分割、按需加载等功能,可以帮助开发者构建高性能的 Angular 应用程序。
- Nx 还集成了许多其他工具和功能,如测试、部署、CI/CD 等,为开发者提供了全面的解决方案。
 
- Storybook:
- Storybook 是一个用于构建 UI 组件的开源工具,它与 Angular 和其他主流前端框架都有良好的集成。
- Storybook 可以帮助开发者更快速地构建、测试和展示 UI 组件,类似于 Next.js 中的 Storybook 功能。
 
- Angular CLI 增强工具:
- 代码分割、按需加载等。
 
angular.json
项目构建和开发服务器配置的详细信息。这个文件允许开发者自定义构建过程,包括配置不同的构建目标、添加自定义的构建选项、指定源文件和资源文件的位置等。
- projects:包含项目列表,每个项目都有自己的配置。
- schematics:定义了 Angular CLI 的 schematics 配置,用于生成代码。
- defaultProject:指定默认项目,当运行- ng serve或- ng build等命令时,如果没有指定项目名称,则使用默认项目。
每个项目配置通常包含以下部分:
- architect 定义了构建、测试、运行和打包等操作的配置。
- build:用于构建项目的配置。
- serve:用于启动开发服务器的配置。
- test:用于运行单元测试和端到端测试的配置。
- lint:用于执行代码风格检查的配置。
- e2e:用于运行端到端测试的配置。
 
使用webpack
在 angular.json 文件中,找到 architect.build.options 部分,并添加 customWebpackConfig
"architect": {
  "build": {
...
    "configurations": {
...
    "customWebpackConfig": {
      "path": "./webpack.config.js",
      "mergeStrategies": {
        "externals": "replace"
      }
    }
  }
}