Лучший способ получить данные формы из вложенного компонента в проекте Angular - PullRequest
1 голос
/ 23 сентября 2019

Я работаю над угловым проектом, который имеет следующую ситуацию: есть один компонент (ClientViewComponent), который содержит:

  • Сводка всей клиентской формы в виде меток (ClientOverviewComponent);
  • Различные «подформы», с помощью которых пользователь может переключаться между ними для редактирования (для простоты, давайте сосредоточимся только на одной, называемой ClientGeneralComponent);

Только одна из нихподформы отображаются одновременно для редактирования, но обзор отображает значения всех входных данных в подформах в режиме реального времени.

Так что это то, что существует сейчас (упрощено, чтобы показывать только частипроценты):

ClientViewComponent HTML

    <div class="container overview-container">
      <h2>Client Overview</h2>
      <client-overview
        [data]="clientOverviewData" 
        [generalGroup]="clientForm.controls['generalGroup']"
        (editFrame)="editFrame($event)">
      </client-overview>
    </div>
    <div *ngIf="(currentFrame$ | async)" class="container part-container">
      <h2>Please choose: </h2>  
      <client-general 
        *ngIf="(currentFrame$ | async).code === 'general'" [data]="clientGeneralData" [formGroup]="clientForm.controls['generalGroup']">
      </client-general>
    </div> 

ClientViewComponent TS

ngOnInit() {
    this.clientForm = this.fb.group({
            generalGroup: this.fb.group({
                name: ['', [Validators.required, Validators.maxLength(256)]], 
                code: ['', [Validators.required, Validators.maxLength(32)]], 
                addressGroup: this.fb.group({
                    address: ['', [Validators.required, Validators.maxLength(512)]],
                    address2: ['', [Validators.maxLength(512)]],
                    city: ['', [Validators.required, Validators.maxLength(256)]],
                    state: ['', [Validators.required, Validators.maxLength(64)]],
                    zipCode: ['', [Validators.required, Validators.maxLength(64)]],
                    country: ['', [Validators.required]],
                })                
            })
        });
}

ClientOverviewHTML

<div class="span-container">
            <span [ngClass]="{ 'modified-value' : !isNew && generalGroup.controls['name'].dirty }">* Client Name: {{generalGroup.controls['name'].value}}</span>
            <span [ngClass]="{ 'modified-value' : !isNew && generalGroup.controls['code'].dirty }">* Client Code: {{generalGroup.controls['code'].value}}</span>
            <span [ngClass]="{ 'modified-value' : !isNew && addressGroup.controls['address'].dirty }">* Address: {{addressGroup.controls['address'].value}}</span>        
            <span [ngClass]="{ 'modified-value' : !isNew && addressGroup.controls['address2'].dirty }">Address #2: {{addressGroup.controls['address2'].value}}</span>        
            <span [ngClass]="{ 'modified-value' : !isNew && addressGroup.controls['city'].dirty }">* City: {{addressGroup.controls['city'].value}}</span>        
            <span [ngClass]="{ 'modified-value' : !isNew && addressGroup.controls['state'].dirty }">* State: {{addressGroup.controls['state'].value}}</span>        
            <span [ngClass]="{ 'modified-value' : !isNew && addressGroup.controls['country'].dirty }">* Country: {{country}}</span>        
            <span [ngClass]="{ 'modified-value' : !isNew && addressGroup.controls['zipCode'].dirty }">* Zip Code: {{addressGroup.controls['zipCode'].value}}</span>            
</div>

ClientOverviewComponent TS

export class ClientOverviewComponent implements OnInit {

    @Input('data')
    set Data(value: ClientOverviewData) {
        this.setData(value);
    }

    generalGroup: FormGroup;
    addressGroup: FormGroup;
    @Input('generalGroup') 
    set GeneralGroup(value: FormGroup) {
        this.generalGroup = value;
        this.addressGroup = value.get('addressGroup') as FormGroup;
        this.refreshClientValues();                
}

HTML ClientGeneralComponent представляет собой обычную форму с formGroup и вводит данные с соответствующей formControlName

ClientGeneralComponent TS

export class ClientGeneralComponent implements OnInit {

    @Input('data')
    set Data(value: ClientGeneralData) {
        this.setData(value);
    }

    formGroup: FormGroup;
    addressGroup: FormGroup;
    @Input('formGroup') 
    set FormGroup(value: FormGroup){
        this.formGroup = value;
        this.addressGroup = value.get('addressGroup') as FormGroup;
}

Теперь все это работает отлично.Полная форма работает как задумано, пользователь заполняет входные данные для компонента General, а значения отображаются в обзоре в режиме реального времени.Вопрос встал, когда я начал делать модульные тесты для компонентов.Проблема в том, что для тестового класса для ClientGeneralComponent мне нужно снова объявить группы форм и элементы управления, так же, как это делается в коде ClientViewComponent.Моя идея состояла в том, что, если бы я мог переместить инициализацию общей формы из ClientViewComponent в ClientGeneralComponent (который фактически содержит элементы управления), не было бы необходимости копировать код инициализации формы в тестовом классе, например:

beforeEach(() => {
        fixture = TestBed.createComponent(ClientGeneralComponent);
        component = fixture.componentInstance;
        component.formGroup = fb.group({
            name: ['', [Validators.required, Validators.maxLength(256)]],
            code: ['', [Validators.required, Validators.maxLength(32)]],
            addressGroup: fb.group({
                address: ['', [Validators.required, Validators.maxLength(512)]],
                address2: ['', [Validators.maxLength(512)]],
                city: ['', [Validators.required, Validators.maxLength(256)]],
                state: ['', [Validators.required, Validators.maxLength(64)]],
                zipCode: ['', [Validators.required, Validators.maxLength(64)]],
                country: ['', [Validators.required]]
            })
        });
        fixture.detectChanges();
});

Но если я перенесу код инициализации общей формы в дочернюю форму, я начну получать ошибки «не могу прочитать свойство неопределенных» в компоненте Overview, поскольку он не инициализировал свойство «generalGroup» и его элементы управления.Поэтому у меня вопрос: есть ли способ сделать это, инициализировать группу форм в дочернем компоненте, к которому она принадлежит, и при этом иметь возможность связать ее в родительской форме.

Спасибо за любую помощь!

1 Ответ

1 голос
/ 24 сентября 2019

вот рабочий код для stackblitz .

Он реализован немного иначе, чем я объяснил в комментарии.

ClientForm находится внутри ClientGeneralComponent,На form.valueChange он испускает новый клиентский объект.Это выбирается родительским ClientViewComponent и отправляется другому ClientOverviewComponent.Обнаружение угловых изменений обеспечивает обновление значений во всех компонентах.

client-general.component.ts

import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { Observable, of, Subject } from 'rxjs';
import { startWith, map, takeUntil } from 'rxjs/operators';
import { IClient } from './client';

@Component({
    selector: 'client-general',
    templateUrl: './client-general.component.html'
})
export class ClientGeneralComponent implements OnInit, OnDestroy {

  clientForm: FormGroup;

  @Output()
  clientObj: EventEmitter<IClient>;

  destroy$: Subject<boolean>;

  constructor(private fb: FormBuilder) {
    this.destroy$ = new Subject<boolean>();
    this.clientObj = new EventEmitter<IClient>();
  }

  ngOnInit() {
    this.clientForm = this.fb.group({
      generalGroup: this.fb.group({
                name: ['', [Validators.required, Validators.maxLength(256)]]
      })
    });

    this.clientForm.valueChanges
      .pipe(takeUntil(this.destroy$))
      .subscribe(() => this.clientObj.emit(this.clientForm.getRawValue()));
  }

  ngOnDestroy(): void {
    this.destroy$.next(true);
    this.destroy$.unsubscribe();
  }
}

client-general.component.html

<div [formGroup]="clientForm.controls['generalGroup']">
    <h3>General</h3>
    <div>
        <span>Client Name</span>
    </div>
    <div>
        <input type="text" id="name" name="name" formControlName="name"/>    
    </div>
</div>

client-view.component.ts

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { IClient } from './client';

@Component({
  selector: 'client-view',
  templateUrl: './client-view.component.html'
})
export class ClientViewComponent implements OnInit  {

  clientObj: IClient;

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
  }
} 

client-view.component.html

<div>
  <h2>Client Overview</h2>
  <client-overview
  [clientObj]="clientObj"></client-overview>
</div>
<div>
  <h2>Please choose: </h2>  
  <client-general (clientObj)="clientObj=$event"></client-general>
</div>

client-Overview.component.ts

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { IClient } from './client';

@Component({
    selector: 'client-overview',
    templateUrl: './client-overview.component.html'
})
export class ClientOverviewComponent implements OnInit {

    @Input()
    clientObj: IClient;

    ngOnInit() {        

    }
}

client-Overview.component.html

<div>
    <div>            
        <h3>General</h3>
        <div>
            <span>Name: {{clientObj?.generalGroup.name}}</span>
        </div>
    </div>
</div>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...