Taiga UIコンポーネントライブラリの進化の過程で、いくつかのより複雑なコンポーネントが、その値を他の基本コンポーネントの@Inputに渡すためだけに@Inputを持っていることに気付き始めました。時々、3層でもそのような入れ子があります。
コントローラと呼ばれるいくつかのトリッキーなディレクティブを使用してそれを行いました。彼らは入れ子の問題を完全に解決し、ライブラリの重量を減らしました。
この記事では、この概念とAngularのDI機能のおかげで、すべての入力フィールドに共通の設定システムをどのように編成したかを示します。
以前の「タイガ」のテキストフィールド:コントローラーを使用できる場合の良い例
PrimitiveTextfieldと呼ばれる基本的な入力コンポーネントがあります。
このコンポーネントは、ラッパーを使用したテーマのようなスタイルのネイティブ入力です。Angularフォームでは機能せず、本格的なコントロールを構築するために必要です。
テキストフィールドの最初のバージョンは非常に単純で、いくつかの複合入力コンポーネントで使用されていました。しかし、すぐにそれはより複雑になり始めました:新しい機能が追加され、コンポーネントの@Inputsの数はますます増えました。
«» Textfield 17 . :
@Input’ , , . , textfield - 17 .
@Input’ , . : @Inputs — . 10 , . .
, .
@Input’ , . , , : ( ).
@Input’ , . , . - :
@Directive({
selector: '[tuiHintContent]'
})
export class TuiHintControllerDirective {
@Input('tuiHintContent')
content: PolymorpheusContent = ’’;
@Input('tuiHintDirection')
direction: TuiDirection = 'bottom-left';
@Input('tuiHintMode')
mode: TuiHintMode | null = null;
}
— @Input’ , . “tuiHintContent”, .
. DI . , .
@Input’ OnPush-, @Input’. , , @Input . Controller, :
export abstract class Controller implements OnChanges {
readonly change$ = new Subject<void>();
ngOnChanges() {
this.change$.next();
}
}
ngOnChanges, . :
@Directive({
selector: '[tuiHintContent]'
})
export class TuiHintControllerDirective extends Controller {
// ...
}
, change$ . — ChangeDetectorRef, markForCheck change$. , :
constructor(
@Inject(ChangeDetectorRef) private readonly changeDetectorRef: ChangeDetectorRef,
@Optional()
@Inject(TuiHintControllerDirective)
readonly hintController: TuiHintControllerDirective | null,
) {
if (!hintController) {
return;
}
hintController.change$.pipe(takeUntil(this.destroy$)).subscribe(() => {
changeDetectorRef.markForCheck();
});
}
. — .
, “tuiHintContent” textfield .
: - @Input’ . .
, : , .
, null, DI- Angular:
constructor(
@Inject(TUI_HINT_WATCHED_CONTROLLER)
readonly hintController: TuiHintControllerDirective,
) {}
. TUI_HINT_WATCHED_CONTROLLER :
export const TUI_HINT_WATCHED_CONTROLLER = new InjectionToken('watched hint controller');
export const HINT_CONTROLLER_PROVIDER: Provider = [
TuiDestroyService,
{
provide: TUI_HINT_WATCHED_CONTROLLER,
deps: [[new Optional(), TuiHintControllerDirective], ChangeDetectorRef, TuiDestroyService],
useFactory: hintWatchedControllerFactory,
},
];
export function hintWatchedControllerFactory(
controller: TuiHintControllerDirective | null,
changeDetectorRef: ChangeDetectorRef,
destroy$: Observable<void>,
): Controller {
if (!controller) {
return new TuiHintControllerDirective();
}
controller.change$.pipe(takeUntil(destroy$)).subscribe(() => {
changeDetectorRef.markForCheck();
});
return controller;
}
, HINT_CONTROLLER_PROVIDER. “providers” , deps ChangeDetectorRef TuiDestroyService. , ngOnDestroy , ( , ).
:
@Component({
//...
providers: [HINT_CONTROLLER_PROVIDER],
})
export class TuiPrimitiveTextfieldComponent {
constructor(
//...
@Inject(TUI_HINT_WATCHED_CONTROLLER)
readonly hintController: TuiHintControllerDirective,
) {}
}
, . @Input’ .
: DI , .
: hintWatchedControllerFactory , . , .
?
. @Input’ , . : , — , . , . DI , .
-, , . — .
DI , , , . , , DI, API.