Как вы передаете FormGroups и FormArrays в рекурсивный механизм глубоко вложенных компонентов? - PullRequest
0 голосов
/ 18 февраля 2020

Я создаю микро-приложение для редактирования JSON объектов перед сохранением их в моей базе данных. Объект JSON взят из другого микроприложения, которое я создал для анализа кода SVG и создания объекта данных в форме интерфейсов, приведенных ниже.

export interface OvaadSvgStyleProperty{
    property: string;
    setting: string;
  }

  export interface OvaadGraphicAttribute{
    attribute: string;
    setting: string;
    bind? : string;
  }

  export interface OvaadGraphicObject{
    element: string;
    selfClosing: boolean;
    attributes: OvaadGraphicAttribute[];
    styles?: OvaadSvgStyleProperty[];
    subElements?: OvaadGraphicObject[];
  }

  export interface ViewBoxParameters{
    x: string;
    y: string;
    width: string;
    height: string;
  }

  export interface OvaadSvgDataObject extends TitleCore{
      graphicId: string;
      viewBox: ViewBoxParameters;
      coreAttributes: OvaadGraphicAttribute[];
      coreStyles: OvaadSvgStyleProperty[];
      elements: OvaadGraphicObject[];
  }

Если вы обратите внимание на интерфейс OvaadGraphicObject, вы увидите свойство subElements имеет тип OvaadGraphicObject[], который предназначен для обработки экземпляров тегов <g> и других элементов svg, которые потенциально могут вложить вещи в тысячи слоев глубиной, если бы это было необходимо по какой-то причине.

I ' До сих пор я училась через мою борьбу с Angular Forms, и ей нравится, когда форма является единственной монолитной c вещью, поэтому я решила создать семейство функций для генерации формы и передачи значений из JSON объект и передача заполненного FormGroup в мою форму с [formGroup]="myFormVar".

. Это обеспечивает FormGroup, сформированный в соответствии со всеми значениями, переданными, как ожидалось. Если я обращаюсь с данными как с любыми другими данными и передаю их в @Input() s, все ломается и идет туда, куда нужно go, но, к сожалению, оно разрывает связь внутри формы. Вы можете просмотреть демонстрацию stackblitz здесь, чтобы увидеть весь механизм до сих пор, но ради этого поста я сосредоточусь на аспекте elements формы, который в значительной степени раскроет загадку того, как для обработки всего остального.

Для начала функция, которую я использую для создания элемента FormGroup, выглядит следующим образом

export function CreateGraphicElementForm(data?: OvaadGraphicObject): FormGroup{
    let elementForm: FormGroup = new FormGroup({
      element     : new FormControl((data ? data.element    : ''), Validators.required),
      selfClosing : new FormControl((data? data.selfClosing : false), Validators.required),
      attributes  : new FormArray([]),
      styles      : new FormArray([]),
      subElements : new FormArray([])
    });

    if(data && data.attributes.length > 0){
      data.attributes.forEach((a: OvaadGraphicAttribute)=>{
        let attrArray: FormArray = elementForm.get('attributes') as FormArray;
        attrArray.push(CreateAttributeForm(a));
      });
    }

    if(data && data.styles.length > 0){
      data.styles.forEach((a: OvaadSvgStyleProperty)=>{
        let styleArray: FormArray = elementForm.get('styles') as FormArray;
        styleArray.push(CreateStylePropertyForm(a));
      });
    }

    if(data && data.subElements){
      data.subElements.forEach((a:OvaadGraphicObject)=>{
        let subElementArray: FormArray = elementForm.get('subElements') as FormArray;
        subElementArray.push(CreateGraphicElementForm(a));
      });

    }

    return elementForm as FormGroup;
  }

Так что в пределах этого FormGroup есть 2 FormControl s и 3 FormArray s, которые необходимо передать в соответствующие компоненты. Я обращаюсь с этой частью с помощью того, что я называю, и Element List Component, который принимает массив этих FormGroup и отвечает за их добавление или удаление. Тогда у меня есть ElementFormComponent, который получает FormGroup от ElementListComponent. Затем, конечно, ElementListComponent получает данные от родительского компонента, где создается форма, которую я называю SvgObjectFormComponent. Вот как выглядят ElementListComponent и ElementFormComponent.

ElementList.component.ts

export class ElementListComponent implements OnInit {

  @Input() ElementListData: FormArray;

  constructor() { }

  ngOnInit() {
  }

  addElement(): void{
    const newElement: FormGroup = CreateGraphicElementForm();
    let elementList: FormArray = <FormArray>this.ElementListData as FormArray;
    console.log(newElement);
    elementList.push(newElement);
  }

  deleteElement(item: number): void{
    let elementList:FormArray = this.ElementListData as FormArray;

    console.log(item);

    elementList.removeAt(item);
  }

}

ElementList.component. html

<section [formGroup]="ElementListData">


    <article *ngIf="!ElementListData">
        <p>loading</p>
    </article>

    <article *ngIf="ElementListData">

        <h5 *ngIf="ElementListData.controls.length === 0" class="articleText">no elements</h5>

        <section *ngIf="ElementListData.controls.length > 0">

            <article *ngFor="let item of ElementListData.controls; let i = index" class="list-grid">

                <p class="index-area articleText">{{i}}</p>


       <!-- I removed attempts at passing in data to avoid discussing things we already know are wrong-->
                <element-form-component class="component-area"></element-form-component>



                <article class="delete-area">
                    <button (click)="deleteElement(i)">delete</button>
                </article>


            </article>

        </section>

    </article>

    <article>
        <button (click)="addElement()">add</button>
    </article>

</section>


ElementForm.component.ts

export class ElementFormComponent implements OnInit, ControlValueAccessor {

  @Input() ElementFormData: FormGroup;

  constructor() { }

  ngOnInit() {}

}

ElementForm.component. html

<section [formGroup]="ElementFormData">
    <article class="text-control-section">
        <label class="control-label">
            element:
            <input type="text" class="text-control" formControlName="element" />
        </label>

        <label class="control-label">
            self closing:
            <input type="text" class="text-control" formControlName="selfClosing" />
        </label>
    </article>



    <!-- again I eliminated failed attempts at passing objects into components to reduce confusion -->
    <article>
        <h3>Attributes</h3>
        <attribute-list-component></attribute-list-component>
    </article>

    <article>
        <h3>Styles</h3>
        <style-list-component></style-list-component>
    </article>



    <section>

        <h3>Sub Elements</h3>

        <p *ngIf="ElementFormData.controls.subElements.length === 0">no sub elements</p>

        <article *ngFor="let item of ElementFormData.controls.subElements; let i = index" class="list-grid">

            <p class="index-area">{{i}}</p>


           <!-- this is where the recursive behavior begins if the element has nested elements -->
            <element-form-component class="component-area"></element-form-component>

            <article class="delete-area">
                <button>delete element</button>
            </article>

        </article>

    </section>




</section>

Я пробовал все, используя [(ngModel)] и [FormControl] против formControlName против [formControlName] и возиться с ControlValueAccessor, просто чтобы обнаружить, что он предназначен только для одиночных FormControl с. На другой вопрос, который я задал, и который был основан на ControlValueAccessor, кто-то предложил мне добавить это к своим поставщикам компонентов

viewProviders: [
   { provide: ControlContainer, useExisting: FormGroupDirective }

, что устраняет необходимость в ControlValueAccesssor, позволяющем всем элементам управления быть доступ, но это предполагает, что я использую свой компонент только в одном месте в форме синхронизации только с одним FormGroup, когда, как вы видите из этого примера, мне нужно выяснить, как обеспечить рекурсивное соединение с вершиной дерева компонентов , Как можно достичь такого типа поведения с помощью Angular Forms?

Мой стековый блик содержит все компоненты, функции и объект данных для демонстрации, а также мои самые последние попытки заставить это поведение работать, которое было через ControlValueAcessor. Кто-то, пожалуйста, помогите мне разобраться, как выполнить sh, потому что Angular Формы - это единственное, что я не смог получить gr asp за все время использования платформы, потому что не знал что бы то ни было, я не могу найти дорогу перед собой.

1 Ответ

0 голосов
/ 18 февраля 2020

Я сталкивался с этой статьей , в которой рассказывается, как выполнить sh такое поведение с помощью ChangeDetectionStrategy.push в наших компонентах. Мы добавляем его в наш компонент следующим образом:

@Component({
    selector       : 'element-list-component',
    templateUrl    : 'element-list.component.html',
    StyleUrls      : ['element-list.component.css'],
    // add change detection here
    changeDetection: ChangeDetectionStrategy.push
})

export class ElementListComponent {

    @Input() ElementListData : FormGroup;

    //.......
}

Затем в нашем классе Component мы можем использовать обычный @Input() для передачи объекта формы, как мы делаем с любым другим фрагментом данных. В родительском компоненте нам нужно передать объект, подобный этому

<section [formGroup]="FormDataVar">

    <element-list-component [ElementListData]="FormDataVar.controls.elements"></element-list-component

</section>

Оттуда мы подключаем FormControls к нашим входам с помощью formControlName="yourControl", и все остается синхронизированным с вершиной дерева :).

...