Архитектура интеграции кастомных UI-компонентов в Angular строится вокруг директив и адаптеров формы. Основная задача директивы для Tom Select — связать imperative API библиотеки с реактивной моделью Angular Forms, обеспечив двустороннюю синхронизацию состояния, корректное управление жизненным циклом и минимизацию побочных эффектов при изменениях данных.
Tom Select работает поверх стандартного <select>
или <input> и преобразует его в управляемый компонент
с поиском, тегами, кастомными рендерами и асинхронной подгрузкой. В
Angular это требует:
Директива выступает как прослойка между Angular Forms API и внутренним состоянием Tom Select.
Ключевой механизм интеграции — реализация интерфейса
ControlValueAccessor. Он определяет контракт:
Типовая структура:
writeValue(value: any) — обновление UIregisterOnChange(fn) — регистрация callback изменения
значенияregisterOnTouched(fn) — регистрация touch-событияsetDisabledState(isDisabled) — управление disabled
состояниемИменно через этот слой Angular абстрагирует DOM-логику.
Инициализация выполняется после появления элемента в DOM, обычно в
ngAfterViewInit.
Основная схема:
<select>Логика:
При создании учитываются опции:
createoptionsvalueFieldlabelFieldsearchFieldplugins@Directive({
selector: '[appTomSelect]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TomSelectDirective),
multi: true
}
]
})
export class TomSelectDirective implements ControlValueAccessor, AfterViewInit, OnDestroy {
private ts: any;
private onChange: (value: any) => void = () => {};
private onTouched: () => void = () => {};
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
this.initTomSelect();
}
private initTomSelect(): void {
this.ts = new TomSelect(this.el.nativeElement, {
onChange: (value: any) => this.onChange(value),
onBlur: () => this.onTouched()
});
}
writeValue(value: any): void {
if (this.ts) {
this.ts.setValue(value, true);
}
}
registerOnChange(fn: any): void {
this.onCha nge = fn;
}
registerOnTouched(fn: any): void {
this.onTouc hed = fn;
}
setDisabledState(isDisabled: boolean): void {
if (this.ts) {
isDisabled ? this.ts.disable() : this.ts.enable();
}
}
ngOnDestroy(): void {
this.ts?.destroy();
}
}
Основная сложность заключается в предотвращении циклических обновлений:
Чтобы избежать зацикливания:
Пример логики:
writeValue обновляет UI без emit событийonChange вызывает Angular callback только при
пользовательском вводеВ связке с FormControl директива становится прозрачным
адаптером:
setValueresetОсобенности:
Tom Select поддерживает динамическое добавление и удаление опций. В
Angular директиве это реализуется через @Input():
Стратегия обновления:
addOptionclearOptionsTom Select часто используется с remote data source. В директиве это реализуется через:
Алгоритм:
loadaddOptionВажно контролировать:
Жизненный цикл директивы критичен:
ngAfterViewInit — создание инстансаngOnChanges — обновление optionsngOnDestroy — уничтожение и освобождение памятиОсобое внимание уделяется:
Интеграция с Angular требует контроля зон:
runOutsideAngularОптимизации:
Tom Select позволяет кастомизировать отображение:
В Angular это реализуется через:
Типичные проблемы:
Стратегии:
Директива может расширяться до полноценного адаптера:
В сложных сценариях Tom Select используется как view-layer компонент, а Angular управляет исключительно состоянием данных, оставляя DOM полностью библиотеке.