面试题答案
一键面试设计思路
- 理解内置指令原理:深入了解像
NgModel
这样的内置指令的工作机制。NgModel
实现双向数据绑定依赖于ControlValueAccessor
接口,该接口定义了三个方法writeValue
、registerOnChange
和registerOnTouched
,以及一个可选的setDisabledState
方法。 - 自定义指令设计原则:
- 复用性:确保自定义指令具有通用的功能,可以在多个组件中复用。例如,为输入框添加特定样式或行为的指令,不应该与特定组件的业务逻辑紧密耦合。
- 可维护性:保持指令逻辑清晰,尽量单一职责。将复杂逻辑拆分成更小的函数或服务,便于理解和修改。
- 性能优化:避免在指令的生命周期钩子函数中执行大量计算或频繁的 DOM 操作。使用
ChangeDetectorRef
来精确控制变化检测,减少不必要的性能开销。
- 与
NgModel
配合:自定义指令如果要和NgModel
协同工作,同样需要实现ControlValueAccessor
接口。这样可以将自定义指令融入到 Angular 的表单系统中,实现双向数据绑定。
代码示例
- 创建自定义指令:
import { Directive, forwardRef, ElementRef, HostListener, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputDirective), multi: true }; @Directive({ selector: '[appCustomInput]', providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR] }) export class CustomInputDirective implements ControlValueAccessor { @Input() appCustomInput: string; value: any; onChange = (_: any) => {}; onTouched = () => {}; constructor(private el: ElementRef) {} writeValue(obj: any): void { this.value = obj; this.el.nativeElement.value = obj; } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } @HostListener('input', ['$event.target.value']) onInputChange(value: any) { this.value = value; this.onChange(value); this.onTouched(); } }
- 在组件模板中使用:
在组件类中定义<input type="text" appCustomInput [(ngModel)]="data">
data
属性:import { Component } from '@angular/core'; @Component({ selector: 'app-custom-input-demo', templateUrl: './custom - input - demo.component.html', styleUrls: ['./custom - input - demo.component.css'] }) export class CustomInputDemoComponent { data: string = ''; }
测试策略
- 单元测试:
- 测试
writeValue
方法:验证指令是否正确更新 DOM 元素的值。 - 测试
registerOnChange
和registerOnTouched
方法:确保回调函数被正确注册。 - 测试
onInputChange
方法:模拟输入事件,验证onChange
和onTouched
回调是否被调用,以及值是否正确更新。 - 使用 Angular 的
TestBed
和ComponentFixture
来创建组件实例并测试指令。
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CustomInputDirective } from './custom - input.directive'; import { FormsModule } from '@angular/forms'; import { Component } from '@angular/core'; @Component({ template: `<input type="text" appCustomInput [(ngModel)]="data">` }) class TestComponent { data: string = ''; } describe('CustomInputDirective', () => { let component: TestComponent; let fixture: ComponentFixture<TestComponent>; let inputElement: HTMLInputElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule], declarations: [CustomInputDirective, TestComponent] }); fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; inputElement = fixture.nativeElement.querySelector('input'); fixture.detectChanges(); }); it('should update value on writeValue', () => { const customDirective = fixture.debugElement.query((de) => de.directiveInstance instanceof CustomInputDirective).injector.get(CustomInputDirective); customDirective.writeValue('test'); expect(inputElement.value).toBe('test'); }); it('should call onChange on input', () => { const customDirective = fixture.debugElement.query((de) => de.directiveInstance instanceof CustomInputDirective).injector.get(CustomInputDirective); spyOn(customDirective, 'onChange'); inputElement.value = 'new value'; inputElement.dispatchEvent(new Event('input')); expect(customDirective.onChange).toHaveBeenCalledWith('new value'); }); });
- 测试
- 集成测试:
- 测试自定义指令与
NgModel
的双向数据绑定是否正常工作。 - 验证组件与指令集成后,数据在组件和输入框之间是否能正确同步。
- 使用
Protractor
或Cypress
等端到端测试框架来模拟用户操作,检查双向数据绑定功能是否符合预期。例如,在输入框中输入值,检查组件中对应数据是否更新;修改组件数据,检查输入框的值是否同步更新。
- 测试自定义指令与