Wie wir grundlegende Taiga-UI-Komponenten flexibler machen: Das Konzept von Komponentencontrollern in Angular

Im Laufe der Entwicklung unserer Taiga UI-Komponentenbibliothek stellten wir fest, dass einige komplexere Komponenten @Input haben, nur um ihren Wert an den @Input unserer anderen Basiskomponente in sich selbst zu ĂĽbergeben. Manchmal gibt es eine solche Verschachtelung sogar in drei Schichten.





Wir haben es mit einigen kniffligen Anweisungen gemacht, die als Controller bezeichnet werden. Sie lösten das Problem der Verschachtelung vollständig und reduzierten das Gewicht der Bibliothek.





In diesem Artikel werde ich Ihnen zeigen, wie wir dank dieses Konzepts und der DI-Funktionen in Angular ein gemeinsames Einstellungssystem fĂĽr alle Eingabefelder organisiert haben.





Textfeld in der ehemaligen "Taiga": ein guter Fall, wenn Sie Controller verwenden können

Wir haben eine grundlegende Eingabekomponente namens Primitive Textfield.





Diese Komponente ist eine native Eingabe, die wie unser Thema gestaltet ist und einen Wrapper dafĂĽr enthält. Es funktioniert nicht mit Angular-Formularen und wird zum Erstellen vollwertiger Steuerelemente benötigt. 





Die allererste Version des Textfelds war recht einfach und wurde in mehreren zusammengesetzten Eingabekomponenten verwendet. Bald wurde es jedoch komplizierter: Neue Funktionen wurden hinzugefĂĽgt, und die Anzahl der @ -Eingaben fĂĽr die Komponente nahm immer mehr zu. 





«» 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.








All Articles