Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

Как сделать компонент с настраиваемым required?

#angular2 #angular


Допустим, компонент А представляет собой блок input-ов.

Этот компонент я добавляю внутрь другого компонента.

В компоненте Б заполняемость input'ов может быть обязательная, а в компоненте С нет.

В обычном случае, если бы не было компонента А и я добавлял бы поля напрямую в Б
и С я бы пометил бы необходимые поля required, а затем повесил бы на кнопку условие,
что поля валидны.

На ум приходит только передача через @Input() компонента А массива с названием необходимых
полей и какое-нибудь булево @Output() свойство, которое может читать компонент Б при
изменении компонента А, показывающие валидность.

Это правильное решение или есть другие более простые варианты?
    


Ответы

Ответ 1



Необязательно это делать через входные параметры, я часто предпочитаю взаимодействие через сервисы, изолируя бизнес логику, а также сервисы позволяют добавить щепотку производительности. К тому же я бы предпочел оперировать константами. Angular построен таким образом, что он будет каждый раз проверять не изменилось ли входное свойство: [requiredFields]="['name', 'surname']" Хотя с другой стороны этот список должен быть константой. Поэтому здесь 2 варианта - использовать сервисы либо константные атрибуты. Компонент A должен находиться в отдельное папке, если это какой-то переиспользуемый компонент и структура должна выглядеть следующим образом: Еще если подключите TS paths, то получится мед, будут понятные и читабельные импорты. Компонент вы добавляете в declarations и exports, тем самым мы получаем так называемый "small&dumb" модуль. Сервис мы не объявляем в провайдерах этого модуля, сервис будет использоваться родительскими компонентами B и C, которые будут проджектить этот компонент A. Сервис будет представлять из себя нечто подобное: interface RequiredFieldsState { allFieldsRequired: boolean; requiredFields?: string[]; } @Injectable() export class RequiredFieldsService implements OnDestroy { private state$ = new BehaviorSubject(null); public ngOnDestroy(): void { this.state$.complete(); } public setState(state: RequiredFieldsState): void { this.state$.next(state); } public getSnapshot(): RequiredFieldsState { return this.state$.getValue(); } } Сервис мы будем добавлять в провайдеры компонентов B и C: import { RequiredFieldsService } from '@app/shared/A'; @Component({ selector: 'app-b', template: ` `, providers: [RequiredFieldsService] }) export class BComponent {} У компонента A также должен быть доступ к инстансу сервиса RequiredFieldsService, также я еще хотел бы, чтоб Angular начал поиск зависимости с родительского инжектора и пропустил текущий: import { SkipSelf } from '@angular/core'; export class AComponent { constructor( @SkipSelf() private requiredFieldsService: RequiredFieldsService ) {} } В родительском компоненте вы просто вызываете метод setState сервиса с нужными параметрами: import { RequiredFieldsService } from '@app/shared/A'; @Component({ selector: 'app-b', template: ` `, providers: [RequiredFieldsService] }) export class BComponent { constructor( @Self() requiredFieldsService: RequiredFieldsService ) { requiredFieldsService.setState({ allFieldsRequired: false }); } } Декоратор @Self говорит Angular получить зависимость для BComponent только из его собственного инжектора. В AComponent вы получаете текущее состояние, проводите некоторые инициализации и сеттите атрибуты required: @Component({ selector: 'app-a', template: `
` }) export class AComponent { public required = this.requiredFieldsService.getSnapshot().allFieldsRequired; constructor( @SkipSelf() private requiredFieldsService: RequiredFieldsService ) {} } Если же мы хотим, чтобы 1 инпут был required, а второй нет, то соответственно сеттим состояние следующим образом: requireFieldsService.setState({ allFieldsRequired: false, requiredFields: ['name'] }); В самом сервисе RequiredFieldsService я бы создал такой хелпер метод: public inputShouldBeRequired({ name }: HTMLInputElement): boolean { const { requiredFields } = this.getSnapshot(); if (Array.isArray(requiredFields)) { return requiredFields.includes(name); } return false; } Использовал бы его в шаблоне следующим образом: Как быть с константными атрибутами? Декоратор @Attribute позволяет получить доступ к строковому атрибуту, который биндится к элементу. Angular сеттит его 1 раз и забывает про него. Как использовать? import { Attribute } from '@angular/core'; export class AComponent { constructor( @Attribute('requiredFields') requiredFields: string | null ) {} } Биндинг один в один как и входного свойства: Квадратные скобки использовать нельзя, все что внутри кавычек - строка!. Разница в том, что доступ в входным параметрам мы можем получить только в ngOnChanges, в отличии от атрибутов, которые доступны уже в конструкторе на этапе инициализации, потому что на этапе компиляции уже известно значение. Как нам превратить строку ['name'] в массив required инпутов - распарсить: @Component({ selector: 'app-a', template: `
` }) export class AComponent { public requiredFields: string[] = []; constructor( @Attribute('requiredFields') requiredFields: string | null ) { this.setRequireFields(requiredFields); } private setRequireFields(requiredFields: string | null): void { if (requiredFields === null) { return; } this.requiredFields = JSON.parse(requiredFields.replace(/\'/g, "\"")); } public inputShouldBeRequired({ name }: HTMLInputElement): boolean { return this.requiredFields.includes(name); } } И кстати во втором варианте нам не нужен сервис, мы засеттили requiredFields 1 раз на этапе инициализации и забыли про него, тем самым Angular не будет производить какие либо проверки.

Комментариев нет:

Отправить комментарий