简体   繁体   中英

Angular. Reactive form two way data binding

After data loaded i call reInit form. After manipulate with data from html template class didn't updated and at this moment i use this:

Object.assign(this.customer, this.customerForm.getRawValue());

But I know that this is bad solution, and each every key and update data by conditions isn't good way. Maby some easy way exists?

I have some class, for Example:

export class Customer {
    id: number|null = null;
    short_name: string|null = null;
    full_name: string|null = null;
    contacts: Contact[] = [];
    constructor(data?: Foo) { /* ... */ }
}
export class Contact {
    id: number|null = null;
    name: string|null = null;
    general: boolean;
    emails: {email: string|null, general: boolean}[];
    phones: {phone: string|null, general: boolean}[];
}

for create all data empty, for existed Customer some filds filled.

now create a form:

  // convenience getters for easy access to form fields
  get controls(): {[key: string]: AbstractControl} { return this.customerForm.controls; }
  get contacts(): FormArray { return this.controls.contacts as FormArray; }

  emails(contact: any): FormArray  { return contact.controls.emails as FormArray; }
  phones(contact: any): FormArray  { return contact.controls.phones as FormArray; }

private initForm(customer?: Customer) {
    this.customerForm = this.formBuilder.group({
      short_name: [customer?.short_name, Validators.required],
      full_name: [customer?.full_name, Validators.required],
      user_id: [customer?.user_id, Validators.required],
      site: [customer?.site],
      contacts: new FormArray([])
    });
    this.customer.contacts.map(c => this.addContact(c));
  }
private addContact(c: Contact): FormGroup {
    const contactFormGroup = this.formBuilder.group({
      id: [c?.id],
      name: [c?.name, [
        Validators.required,
        CustomerContactValidator.createContact,
        CustomerContactValidator.onlyOneGeneralEmail,
        CustomerContactValidator.onlyOneGeneralPhone,
      ]],
      general: [c?.general],
      position: [c?.position],
      comment: [c?.comment],
      emails: new FormArray([]),
      phones: new FormArray([])
    });
    this.contacts.push(contactFormGroup);
    c.emails.map(ed => this.addContactEmail(contactFormGroup, ed));
    c.phones.map(pd => this.addContactPhone(contactFormGroup, pd));
    return contactFormGroup;
  }



  private addContactEmail(contactFormGroup: FormGroup, ed: ContactEmail, contactFormGroupIndex: number|null = null): void {
    const emails = contactFormGroup.controls.emails as FormArray;
    if (emails) {
      const contactEmailFormGroup = this.formBuilder.group({
        type: [ed?.type],
        general: [ed?.general],
        email: [ed?.email, [Validators.required, Validators.email]]
      });
      emails.push(contactEmailFormGroup);
      if (contactFormGroupIndex !== null) {
        setTimeout(() => {
          document.getElementById(`email-${contactFormGroupIndex}-${emails.length-1}`)?.focus();
        }, 50);
      }
      this.afterManipulateWithContactContacts(contactFormGroup);
    }
  }

  private addContactPhone(contactFormGroup: FormGroup, pd: ContactPhone, contactFormGroupIndex: number|null = null): void {
    const phones = contactFormGroup.controls.phones as FormArray;
    if (phones) {
      const contactPhoneFormGroup = this.formBuilder.group({
        type: [pd?.type],
        general: [pd?.general],
        phone: [pd?.phone, [Validators.required, Validators.pattern(PHONE_PATTERN)]]
      });
      phones.push(contactPhoneFormGroup);
      if (contactFormGroupIndex !== null) {
        setTimeout(() => {
          document.getElementById(`phone-${contactFormGroupIndex}-${phones.length-1}`)?.focus();
        }, 50);
      }
      this.afterManipulateWithContactContacts(contactFormGroup);
    }
  }

and html:

<form class="customer-form" [formGroup]="customerForm">
<ng-container *ngFor="let contact of contacts.controls; let i = index" >
      <div [formGroup]="$any(contact)" fxLayout="column" fxLayout.gt-md="row">
        <div fxFlex="100" fxFlex.gt-md="20" fxFlex.gt-lg="15" [ngClass.gt-md]="'pr-2'">
          <mat-checkbox
            [ngClass.gt-md]="'mr-2'"
            [ngClass.lt-lg]="'mr-1'"
            formControlName="general"
            (change)="changeGeneralContact($any(contact))"
          ></mat-checkbox>
          <mat-form-field floatLabel="never" style="width: calc(100% - 24px)">
            <input formControlName="name" matInput placeholder="ФИО" type="text">
            <mat-error *ngIf="contact.get('name')?.hasError('required')">Заполните <strong>ФИО</strong></mat-error>
            <mat-error *ngIf="contact.get('name')?.hasError('createContact')">Добавьте <strong>email</strong> или <strong>телефон</strong></mat-error>
            <mat-error *ngIf="contact.get('name')?.hasError('onlyOneGeneralEmail')">Только <strong>один email</strong> может быть основным</mat-error>
            <mat-error *ngIf="contact.get('name')?.hasError('onlyOneGeneralPhone')">Только <strong>один телефон</strong> может быть основным</mat-error>
          </mat-form-field>
        </div>
        <div fxFlex="100" fxFlex.gt-md="25" fxFlex.gt-lg="20" [ngClass.gt-md]="'px-2'">
          <div *ngFor="let email of emails($any(contact)).controls; let ei = index">
            <div [formGroup]="$any(email)" class="d-flex align-items-center">
              <mat-checkbox
                [ngClass.gt-md]="'mr-2'"
                [ngClass.lt-lg]="'mr-1'"
                formControlName="general"
                (change)="changeContactGeneralSubContact($any(contact), $any(email), contactSubType.email)"
              ></mat-checkbox>
              <crm-email-edit [formGroup]="$any(email)" floatLabel="never" style="width: calc(100% - 60px)"></crm-email-edit>
              <button
                mat-icon-button
                matTooltip="Удалить email"
                aria-label="Удалить email"
                (click)="removeContactEmailContact(i, ei, $any(contact))"
              >
                <mat-icon color="warn">delete</mat-icon>
              </button>
            </div>
          </div>
          <div>
            <button
              mat-icon-button
              color="primary"
              [matTooltip]="'Добавить email'"
              (click)="createContactEmailContact($any(contact), i)"
            >
              <mat-icon>add_circle</mat-icon>
            </button>
          </div>
        </div>
        <div fxFlex="100" fxFlex.gt-md="25" fxFlex.gt-lg="20"  [ngClass.gt-md]="'px-2'">
          <div *ngFor="let phone of phones($any(contact)).controls; let pi = index">
            <div [formGroup]="$any(phone)">
              <mat-checkbox
                [ngClass.gt-md]="'mr-2'"
                [ngClass.lt-lg]="'mr-1'"
                formControlName="general"
                (change)="changeContactGeneralSubContact($any(contact), $any(phone), contactSubType.phone)"
              ></mat-checkbox>
              <mat-form-field floatLabel="never">
                <input [id]="'phone-'+i+'-'+pi" formControlName="phone" matInput placeholder="Телефон" type="number">
                <mat-error *ngIf="phone.get('phone')?.hasError('required')">Заполните <strong>Телефон</strong> или удалите</mat-error>
                <mat-error *ngIf="phone.get('phone')?.hasError('pattern')"><strong>Телефон</strong> указан не верно</mat-error>
              </mat-form-field>
              <button
                mat-icon-button
                matTooltip="Удалить телефон"
                aria-label="Удалить телефон"
                (click)="removeContactPhoneContact(i, pi, $any(contact))"
              >
                <mat-icon color="warn">delete</mat-icon>
              </button>
            </div>
          </div>
          <div>
            <button
              mat-icon-button
              color="primary"
              [matTooltip]="'Добавить телефон'"
              (click)="createContactPhoneContact($any(contact), i)"
            >
              <mat-icon>add_circle</mat-icon>
            </button>
          </div>
        </div>
        <div fxFlex="10" fxFlex.gt-md="5" [ngClass.gt-md]="'px-2'">
          <button
            mat-mini-fab
            color="warn"
            matTooltip="Удалить контакт"
            aria-label="Удалить контакт"
            (click)="removeContact(i)"
          >
            <mat-icon>delete</mat-icon>
          </button>
        </div>
      </div>
    </ng-container>
    
    <div>
      <button mat-fab (click)="createContact()" aria-label="Добавить контакт" color="primary">
        <mat-icon>person_add</mat-icon>
      </button>
    </div>
  </form>

There is no direct two-way model binding in reactive form, but in a sense has two way form binding.

  1. You provide value to ABC: formControls(['My value'])
  2. ABC controller can be updated from controller or HTML template.

a. In HTML it will be updated by user.

b. In component.ts you can update it by controllers only ABC.PatchValue or setValue.

you can get all the value from getRawValue() or .value , but you cant manipulate these values to update the form directly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM