Как получить проверенные значения в массив для угловой формы управляемой формы (ngModel) - PullRequest
0 голосов
/ 09 января 2019

Я пытаюсь использовать формы на основе шаблонов, привязывая модель в html к новому экземпляру, скажем, Person . Мне не удалось создать правильную привязку для флажков к одному свойству массива в моей модели.

Идея состоит в том, что данные будут поступать из API или другого источника, динамически отображать флажки с помощью *ngFor и связывать то, что выбрано, со свойством моделей Person , которое будет представлять собой массив чисел. Например:

class Person {
  firstName: string;
  someCheckboxPropList: number[];
}

и данные могут быть чем угодно

const dynamicData = [
  { name: 'name1', value: 1 },
  { name: 'name2', value: 2 }
];

мой ожидаемый результат будет примерно таким же, как в [ 1, 2 ], если оба значения будут проверяться и проверяется только второе [ 2 ].

Вот пример того, как может выглядеть файл PersonComponent.ts

@Component({ ... })
export class PersonComponent {

    submitted = false;

    model = new Person();

    constructor() { }

    onSubmit(form) {
        this.submitted = true;
        console.log(form);
    }

}

И где я нахожусь с html-файлом компонентов.

<form (ngSubmit)="onSubmit(form)" #form="ngForm">

    <input type="text" [(ngModel)] name="person.firstName">

    <div *ngFor="let dataItem of dynamicData" >
        <input 
            type="checkbox"
            ngModel
            name="dynamicData"
            [value]="dataItem.value">
        <label>{{dataItem.name}}</label>
    </div>

</form>

Это не работает (и в любом случае является примером кода).

Ответы [ 2 ]

0 голосов
/ 11 января 2019

Если мы хотим, мы можем создать пользовательский элемент управления Form.

В этом случае нам нужны в качестве входных данных источник и столбцы источника - первым будет ключ, а вторым - текст, который появляется.

Я делаю стек-блиц

.html будет

  <check-box-group name="props" [(ngModel)]="person.props"
      [source]="dynamicData" cols="value,name" >
  </check-box-group>

компонент это типичный пользовательский элемент управления

@Component({
  selector: 'check-box-group',
  template: `
      <div class="form-check" *ngFor="let item of source;let i=index">
        <input class="form-check-input" id="{{_name+''+i}}"
             type="checkBox" [ngModel]="_selectedItems[i]"
             (ngModelChange)="setValue($event,i)">
        <label class="form-check-label" for="{{_name+''+i}}">
              {{item[_col]}}
        </label>
      </div>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CheckBoxGroupComponent),
      multi: true
    }
  ]
})
export class CheckBoxGroupComponent implements ControlValueAccessor {

  @Input() source;
  @Input()
  set cols(value:string){ //cols is a string separated by commas
                          //e.g. "value,text", the "key" will be "value" and show the text
    let _cols=value.split(',')
    this._key = _cols[0];
    this._col = _cols[1]
  }
  _selectedItems: any[] = [];
  _key: string;
  _col: string;
  _name:string="";
  onChange;
  onTouched;

  constructor(el:ElementRef) { 
    let name=el.nativeElement.getAttribute('name')
    this._name=name?name:"ck";
  }
  writeValue(value: any[]): void {
    this._selectedItems = this.propsToBoolean(value);
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
  }
  setValue(value: boolean, index: number) {
    this._selectedItems[index] = value;
    this.onChange(this.booleanToProps(this._selectedItems));

  }

  propsToBoolean(props): any[] {
    console.log(props);
    return props ? this.source.map((x: any) => props.indexOf(x[this._key]) >= 0)
      : this.source.map(x => false);

  }
  booleanToProps(propsBoolean: boolean[]) {
    let props: any[] = [];
    if (propsBoolean) {
      propsBoolean.forEach((item, index) => {
        if (item)
          props.push(this.source[index][this._key])
      })
    }
    return props;

  }

}

Обновление: добавить проверки

когда у нас есть пользовательский компонент формы и мы хотим сделать «проверку», у нас есть два варианта: выполнить проверку вне компонента или выполнить проверку внутри компонента. Для второго варианта мы должны добавить в качестве провайдера: NG_VALIDATORS ,,

{
  provide: NG_VALIDATORS,
  useExisting: forwardRef(() => CheckBoxGroupComponent),
  multi: true,
}

И добавить функцию проверки

validate(control: AbstractControl): ValidationErrors | null{
    ...your logic here.., e.g.
    if (!this._selectedItems.find(x=>x))
       return {error:"you must select one option at last"}

    return null
  }

Ну, есть еще кое-что, что мы должны сделать, это решить, когда коснуться нашего пользовательского элемента управления. Помните, что элемент управления трогается при потере фокуса после его получения. мы можем сделать это (размытие) нашего флажка (или заключить элемент управления в div с tabindex = 0)

 <input type="checkbox" .... (blur)="onTouched()">

Последний шаг - сделать выдавать ошибку или нет, мы добавляем атрибут к элементу управления. Мне нравится, что если мы добавим атрибут isRequired, проверьте ошибку, иначе нет. Таким образом, мы добавляем новое свойство _isRequired и, в проверке конструктора, имеет ли атрибут

 constructor(el:ElementRef) { 
    let name=el.nativeElement.getAttribute('name');
    this._isRequired=el.nativeElement.getAttribute('isRequired')!=null?true:false;
    this._name=name?name:"ck"; //<--this is necesary for give value to
                               //for="..." in label
    }

И наша проверка учитывает это

  validate(control: AbstractControl): ValidationErrors | null{
    if (!this._isRequired)
      return null;
    ....
  }

ПРИМЕЧАНИЕ. Я обновил пользовательский элемент управления (и мы добавили свойство [customClass])

0 голосов
/ 10 января 2019

идея состоит в том, чтобы иметь две вещи: Person и PersonForm, например,

person={firstName:"Jessy",props:[1,2]
//but 
personForm={firstName:"Jessy",props:[true,true]

Итак, сделайте две функции

  createForm(person) {
    return {
      firstName: person.firstName,
      props: this.dynamicData.map(x => person.props.indexOf(x.value) >= 0)
    }
  }
  retrieveData(personForm) {
    let props: number[] = [];
    personForm.props.forEach((v, index) => {
      if (v)
        props.push(this.dynamicData[index].value)
    }
    )
    return {
      firstName: personForm.firstName,
      props: props
    }
  }

Ну, у нас уже есть все, что нам нужно. Когда мы получили человека, создайте personForm, что это данные, которые мы изменяем в форме. Для отправки просто вызовите retrieveData, чтобы получить значение person.

Когда у нас есть человек, создайте форму человека, например,

this.service.getPerson().subscribe(person=>
    {
       this.personForm=createForm(person)
    }
)

Наша форма

<form *ngIf="personForm" (submit)="sendData(personForm)">
  <input name="firtName" [(ngModel)]="personForm.firstName">
  <div *ngFor="let item of dynamicData;let i=index">
  <input name="{{'prop'+i}}" 
         type="checkBox" [(ngModel)]="personForm.props[i]">{{item.name}}
  </div>
  <button>Submit</button>
</form>
{{personForm|json}}<br/>
{{retrieveData(personForm)|json}}

И наша функция sendData

sendData(personForm)
{
    console.log(this.retrieveData(personForm))
}

Я делаю простой стек

Обновление

ПРИМЕЧАНИЕ. Мы можем использовать оператор spred для поиска свойств, поэтому

  createForm(person) {
    return {
      ...person, //All the properties of person, but
      props: this.dynamicData.map(x => person.props.indexOf(x.value) >= 0)
    }
  }
  retrieveData(personForm) {
    let props: number[] = [];
    personForm.props.forEach((v, index) => {
      if (v)
        props.push(this.dynamicData[index].value)
    }
    )
    return {
      ..personForm, //all the properties of personForm, but
      props: props
    }
  }

ПРИМЕЧАНИЕ 2. В реальном мире люди уходят из службы. Рассмотрим идею, что служба получает / получает «personForm» и помещает функции для преобразования в службу

//in service
getPerson()
{
    return this.httpClient.get("...").map(res=>this.createForm(res))
}
savePerson(personForm)
{
    return this.httpClient.post("...",this.retrieveData(personForm))
}
...