От автора: в этой статье мы рассмотрим, как создаются в Angular директивы, которые взаимодействуют с Элементами управления форм Angular. Мы сделаем это, создав директиву атрибута, которая перехватывает элемент управления базой и изменяет границу элемента узла директивы.
Директива будет поддерживать как шаблонные, так и реактивные формы.
API директив
Начнем с создания директивы и определения ее API. Имена @Input имеют префикс селектора директивы. Мы переименуем их, чтобы имена переменных были короткими и более читаемыми, мы установим некоторые интуитивные значения по умолчанию.
@Directive({ selector: '[appValidationBorder]' }) export class ValidationBorderDirective { private readonly colors: { VALID: string; INVALID: string; PENDING: string; DISABLED: string; }; @Input('appValidationBorderWidth') borderWidth: string; @Input('appValidationBorderStyle') borderStyle: string; }
Словарь colors содержит возможные состояния валидации формы, такие как ключи, и будет использоваться для изменения стиля границы.
NgControl
Чтобы создать действительно многоразовую директиву, нам нужно поддерживать как шаблонные, так и реактивные формы. Но что конкретно мы вводим? Пользователь может использовать любое из следующих действий:
NgModel (шаблонные формы)
<input [(ngModel)]="value" appValidationBorder>
FormControlName (реактивные формы)
<input formControlName="value" appValidationBorder>
FormControlDirective (реактивные формы)
<Input [formControl]="value" appValidationBorder>
К счастью для нас, все они представлены как NgControl.
Это означает, что мы можем включить NgControl, а структура Angular DI предоставит ближайшую директиву элемента управления формы. Мы также обязательно ограничиваем включение с помощью декоратора @Self.
constructor(@Self() ngControl: NgControl) {}
Используя NgControl, мы получаем доступ к таким свойствам, как значение элемента управления, статус валидации, ошибки и многим другим.
Большинство свойств попадают в класс AbstractControl, с которым вы, вероятно, знакомы, если раньше работали с Angular Forms.
Привязка хостов
Теперь, когда у нас есть доступ к элементу управления формы, давайте используем его для отображения или скрытия границы с помощью декоратора @HostBinding.
@HostBinding('style.border-style') get borderStyleCss() { return this.showBorder ? this.borderStyle : null; } @HostBinding('style.border-width') get borderWidthCss() { return this.showBorder ? this.borderWidth : null; } @HostBinding('style.border-color') get borderColorCss() { return this.showBorder ? this.colors[this.ngControl.status] : null; } get showBorder() { return this.ngControl.dirty || this.ngControl.touched; }
Мы будем отображать границу только после того, как пользователь взаимодействует с элементом управления формы.
Для этого мы проверим свойства NgControl dirty и touched. Проверяя dirty и touched, мы не даем отображать границы, пока пользователь не:
изменит значение, переключая элемент управления на dirty,
коснется элемента управления, устанавливая для элемента управления touched.
Универсальная конфигурация
Давайте закончим с предоставлением способа настройки директивы. Важно, чтобы пользовательский интерфейс был последовательным, поэтому было бы неплохо установить конфигурацию директивы один раз и применять его во всем приложении.
Во-первых, давайте создадим интерфейс конфигурации:
export interface ValidationBorderConfig { borderWidth: string; borderStyle: string; colors: { VALID: string; INVALID: string; PENDING: string; DISABLED: string; }; }
Затем мы реализуем статический метод forRoot класса ValidationBorderModule. Он предоставит конфигурацию валидации в токене VALIDATION_BORDER_CONFIG.
@NgModule({ imports: [CommonModule, FormsModule], declarations: [ValidationBorderDirective], exports: [ValidationBorderDirective] }) export class ValidationBorderModule { static forRoot(config: ValidationBorderConfig): ModuleWithProviders { return { ngModule: ValidationBorderModule, providers: [{ provide: VALIDATION_BORDER_CONFIG, useValue: config }] }; } }
Наконец, давайте задействуем конфигурацию внутри директивы с использованием декоратора @Inject.
constructor( @Self() private ngControl: NgControl, @Inject(VALIDATION_BORDER_CONFIG) config: ValidationBorderConfig ) { this.colors = config.colors; this.borderWidth = config.borderWidth; this.borderStyle = config.borderStyle; }
С внедренным методом forRoot у нас есть чистый способ настройки директивы. Мы можем импортировать его один раз в app.module, и он будет настраивать каждую директиву, используемую во всем приложении.
@NgModule({ imports: [ ValidationBorderModule.forRoot({ borderStyle: 'solid', borderWidth: '3px', colors: { VALID: 'green', INVALID: 'red', PENDING: 'yellow', DISABLED: 'silver' } }) ], }) export class AppModule {}
Шаблонный пример
@Component({ selector: 'app-template-forms-example', template: ` <input [(ngModel)]="value" required appValidationBorder> ` }) export class TemplateFormsExampleComponent { value = ''; }
Реактивный пример
@Component({ selector: 'app-reactive-forms-example', template: ` <input [formControl]="formControl" appValidationBorder> `, }) export class ReactiveFormsExampleComponent { formControl = new FormControl('', Validators.required); }
Заключение
Нам удалось создать гибкую директиву атрибутов, которая работает как с шаблонами, так и с реактивными формами Angular. В процессе мы научились использовать NgControl для включения ближайшей директивы формы и применять @HostBinding для отображения статуса формы путем стилизации границы элемента. Мы реализовали удобный способ настройки директивы с помощью метода forRoot.
@Directive({ selector: '[appValidationBorder]' }) export class ValidationBorderDirective { private readonly colors: { VALID: string; INVALID: string; PENDING: string; DISABLED: string; }; @Input('appValidationBorderWidth') borderWidth: string; @Input('appValidationBorderStyle') borderStyle: string; @HostBinding('style.border-style') get borderStyleCss() { return this.showBorder ? this.borderStyle : null; } @HostBinding('style.border-width') get borderWidthCss() { return this.showBorder ? this.borderWidth : null; } @HostBinding('style.border-color') get borderColorCss() { return this.showBorder ? this.colors[this.ngControl.status] : null; } get showBorder() { return this.ngControl.dirty || this.ngControl.touched; } constructor( @Self() private ngControl: NgControl, @Inject(VALIDATION_BORDER_CONFIG) config: ValidationBorderConfig ) { this.colors = config.colors; this.borderWidth = config.borderWidth; this.borderStyle = config.borderStyle; } }
Автор: Tomasz Kula
Источник: https://netbasal.com/
Редакция: Команда webformyself.