programing

Angular2 중첩 템플릿 기반 폼

magicmemo 2023. 9. 3. 12:32
반응형

Angular2 중첩 템플릿 기반 폼

이것은 단지 광기일 뿐이고, 그것의 입력 중 하나가 어린이 구성요소에 있는 형태를 가질 방법이 없는 것처럼 보입니다.

저는 모든 블로그와 튜토리얼 그리고 모든 것을 읽었지만, 이것을 해결할 방법이 없었습니다.

문제는 하위 구성 요소에 어떤 종류의 양식 지시어(ngModel, ngModelGroup 등)가 있으면 작동하지 않는다는 것입니다.

이는 템플릿 기반 양식에서만 발생하는 문제입니다.

다음은 플런커입니다.

import { Component } from '@angular/core';

@Component({
  selector: 'child-form-component',
  template: ` 
  <fieldset ngModelGroup="address">
    <div>
      <label>Street:</label>
      <input type="text" name="street" ngModel>
    </div>
    <div>
      <label>Zip:</label>
      <input type="text" name="zip" ngModel>
    </div>
    <div>
      <label>City:</label>
      <input type="text" name="city" ngModel>
    </div>
  </fieldset>`
})

export class childFormComponent{


}

@Component({
  selector: 'form-component',
  directives:[childFormComponent],
  template: `
    <form #form="ngForm" (ngSubmit)="submit(form.value)">
      <fieldset ngModelGroup="name">
        <div>
          <label>Firstname:</label>
          <input type="text" name="firstname" ngModel>
        </div>
        <div>
          <label>Lastname:</label>
          <input type="text" name="lastname" ngModel>
        </div>
      </fieldset>

      <child-form-component></child-form-component>

      <button type="submit">Submit</button>
    </form>

    <pre>
      {{form.value | json}}
    </pre>

    <h4>Submitted</h4>
    <pre>    
      {{value | json }}
    </pre>
  `
})
export class FormComponent {

  value: any;

  submit(form) {
    this.value = form; 
  }
}

한 가지 간단한 해결책은 제공하는 것입니다.ControlContainerviewProviders다음과 같은 하위 구성 요소 배열:

import { ControlContainer, NgForm } from '@angular/forms';

@Component({
 ...,
 viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class ChildComponent {}

스택블리츠 예제

작동하는 이유를 설명하는 이 기사도 읽어 보십시오.

갱신하다

내포된 모델 기반 양식을 찾는 경우 다음과 유사한 접근 방식이 있습니다.

@Component({
  selector: 'my-form-child',
  template: `<input formControlName="age">`,
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class ChildComponent {
  constructor(private parent: FormGroupDirective) {}

  ngOnInit() {
    this.parent.form.addControl('age', new FormControl('', Validators.required))
  }
}

Ng-run 예제

업데이트 2

정확히 어떤 유형인지 모르는 경우ControlContainer사용자 정의 구성 요소를 래핑한 다음(예: 컨트롤이 FormArray 디렉티브 내부에 있음) 공통 버전을 사용합니다.

import { SkipSelf } from '@angular/core';
import { ControlContainer} from '@angular/forms';

@Component({
 ...,
 viewProviders: [{
   provide: ControlContainer,
   useFactory: (container: ControlContainer) => container,
   deps: [[new SkipSelf(), ControlContainer]],
 }]
})
export class ChildComponent {}

Ng-run 예제

관련된 많은 github 이슈[1][2]를 읽으면서, 나는 각진 아이를 추가할 수 있는 간단한 방법을 찾지 못했습니다.Component의 부모에 대한 통제.ngForm(어떤 사람들은 중첩된 양식, 중첩된 입력 또는 복잡한 컨트롤이라고도 합니다.

여기서 보여드릴 것은 별도의 솔루션을 사용하여 저에게 적합한 해결 방법입니다.ngForm부모와 자녀를 위한 지침완벽하지는 않지만, 제가 거기서 멈췄을 정도로 저를 가깝게 해줍니다.

선언합니다.childFormComponent와 함께ngForm디렉티브(즉, HTML 형식 태그가 아니라 디렉티브만 해당):

<fieldset ngForm="addressFieldsForm" #addressFieldsForm="ngForm">
  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" class="form-control" [(ngModel)]="model.email" name="email" #email="ngModel" required placeholder="Email">
  </div>
  ...

그러면 구성 요소가 다음을 노출합니다.addressFieldsForm속성으로, 템플릿 참조 변수로 자체 내보내기도 합니다.

@Component({
  selector: 'mst-address-fields',
  templateUrl: './address-fields.component.html',
  styleUrls: ['./address-fields.component.scss'],
  exportAs: 'mstAddressFields'
})
export class AddressFieldsComponent implements OnInit {
  @ViewChild('addressFieldsForm') public form: NgForm;
  ....

그러면 부모 양식은 다음과 같은 자식 양식 구성요소를 사용할 수 있습니다.

  <form (ngSubmit)="saveAddress()" #ngFormAddress="ngForm" action="#">
    <fieldset>
      <mst-address-fields [model]="model" #addressFields="mstAddressFields"></mst-address-fields>
      <div class="form-group form-buttons">
        <button class="btn btn-primary" type="submit" [disabled]="!ngFormAddress.valid || !addressFields.form.valid">Save</button>
      </div>
    </fieldset>
  </form>

제출 버튼은 양쪽에서 유효한 상태를 명시적으로 확인합니다.ngFormAddress그리고addressFields양식. 그래야 내가 최소한 양식을 감각적으로 구성할 수 있습니다. 비록 그것이 약간의 보일러 플레이트를 가지고 있지만 말이죠.

또 다른 해결 방법:

@Directive({
    selector: '[provide-parent-form]',
    providers: [
        {
            provide: ControlContainer,
            useFactory: function (form: NgForm) {
                return form;
            },
            deps: [NgForm]
        }
    ]
})
export class ProvideParentForm {}

노드 계층 구조의 맨 위에 있는 하위 구성 요소(ngModel 앞)에 이 지시어를 배치하기만 하면 됩니다.

작동 방식: NgModel은 @Host()를 사용하여 상위 폼의 종속성 조회를 검증합니다.따라서 상위 구성 요소의 양식은 하위 구성 요소의 NgModel에 표시되지 않습니다.그러나 위에서 설명한 코드를 사용하여 하위 구성요소 내부에 주입하고 제공할 수 있습니다.

공식 문서에서:This directive can only be used as a child of NgForm.

그래서 저는 당신이 당신의 아이 구성요소를 다른 것으로 포장할 수 있다고 생각합니다.ngForm상위 구성 요소 결과에 기대@Output하위 구성 요소의.더 명확한 설명이 필요하시면 말씀해주세요.

업데이트: 몇 가지 변경 사항이 있는 플런커입니다. 양식 기반 양식을 제출하기 전에 업데이트된 양식을 들을 수 있는 방법이 없기 때문에 하위 양식을 모델 기반 양식으로 변환했습니다.

지침과 서비스를 사용하여 솔루션을 만들었습니다.모듈에 이러한 코드를 추가하면 템플릿의 양식 수준에서 다른 코드를 변경해야 합니다.이것은 동적으로 추가된 양식 필드와 AOT에서 작동합니다.또한 한 페이지에서 관련이 없는 여러 양식을 지원합니다.여기 플런커가 있습니다. 플런커입니다.

이 명령어는 다음과 같습니다.

import { Directive, Input } from '@angular/core';
import { NgForm } from '@angular/forms';
import { NestedFormService } from './nested-form.service';

@Directive({
    selector: '[nestedForm]',
    exportAs: 'nestedForm'   
})
export class NestedFormDirective {    
    @Input('nestedForm') ngForm: NgForm;
    @Input() nestedGroup: string;
       
    public get valid() {
        return this.formService.isValid(this.nestedGroup);
    }

    public get dirty() {
        return this.formService.isDirty(this.nestedGroup);
    }

    public get touched() {
        return this.formService.isTouched(this.nestedGroup);
    }
    
    constructor(      
        private formService: NestedFormService
    ) { 
        
    }

    ngOnInit() {   
        this.formService.register(this.ngForm, this.nestedGroup);
    }

    ngOnDestroy() {
        this.formService.unregister(this.ngForm, this.nestedGroup);
    } 

    reset() {
        this.formService.reset(this.nestedGroup);
    }
}

이 서비스는 다음과 같습니다.

import { Injectable } from '@angular/core';
import { NgForm } from '@angular/forms';

@Injectable()
export class NestedFormService {

    _groups: { [key: string] : NgForm[] } = {};
      
    register(form: NgForm, group: string = null) {           
        if (form) {
            group = this._getGroupName(group);
            let forms = this._getGroup(group);        
            if (forms.indexOf(form) === -1) {
                forms.push(form);
                this._groups[group] = forms;
            }
        }
    }

    unregister(form: NgForm, group: string = null) {        
        if (form) {
            group = this._getGroupName(group);
            let forms = this._getGroup(group);
            let i = forms.indexOf(form);
            if (i > -1) {
                forms.splice(i, 1);
                this._groups[group] = forms;
            }
        }
    }

    isValid(group: string = null) : boolean {   
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            if (forms[i].invalid)
                return false;
        }
        return true;
    } 

    isDirty(group: string = null) : boolean {   
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            if (forms[i].dirty)
                return true;
        }
        return false;
    } 

    isTouched(group: string = null) : boolean {   
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            if (forms[i].touched)
                return true;
        }
        return false;
    } 

    reset(group: string = null) {
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            forms[i].onReset();
        }
    }

    _getGroupName(name: string) : string {
        return name || '_default';
    }

    _getGroup(name: string) : NgForm[] {        
        return this._groups[name] || [];
    }          
}

양식을 사용하여 상위 구성 요소에서 지시문을 사용하는 방법

import { Component, Input } from '@angular/core';
import { Person } from './person.model';

@Component({
    selector: 'parent-form',
    template: `  
        <div class="parent-box">

            <!--
            ngForm                        Declare Angular Form directive
            #theForm="ngForm"             Assign the Angular form to a variable that can be used in the template
            [nestedForm]="theForm"        Declare the NestedForm directive and pass in the Angular form variable as an argument
            #myForm="nestedForm"          Assign the NestedForm directive to a variable that can be used in the template
            [nestedGroup]="model.group"   Pass a group name to the NestedForm directive so you can have multiple forms on the same page (optional).
            -->

            <form 
                ngForm                  
                #theForm="ngForm" 
                [nestedForm]="theForm"
                #myForm="nestedForm" 
                [nestedGroup]="model.group">        

                <h3>Parent Component</h3> 
                <div class="pad-bottom">
                    <span *ngIf="myForm.valid" class="label label-success">Valid</span>
                    <span *ngIf="!myForm.valid" class="label label-danger">Not Valid</span>
                    <span *ngIf="myForm.dirty" class="label label-warning">Dirty</span>    
                    <span *ngIf="myForm.touched" class="label label-info">Touched</span>    
                </div> 

                <div class="form-group" [class.hasError]="firstName.invalid">
                    <label>First Name</label>
                    <input type="text" id="firstName" name="firstName" [(ngModel)]="model.firstName" #firstName="ngModel" class="form-control" required />
                </div>

                <child-form [model]="model"></child-form>
               
                <div>
                    <button type="button" class="btn btn-default" (click)="myForm.reset()">Reset</button>
                </div>
            </form>   
        </div>
    `
})
export class ParentForm {   
    
    model = new Person();
   
}

그러면 하위 구성 요소에서:

import { Component, Input } from '@angular/core';
import { Person } from './person.model';

@Component({
    selector: 'child-form',
    template: `  
        <div ngForm #theForm="ngForm" [nestedForm]="theForm" [nestedGroup]="model.group" class="child-box">
            <h3>Child Component</h3>
            <div class="form-group" [class.hasError]="lastName.invalid">
                <label>Last Name</label>
                <input type="text" id="lastName" name="lastName" [(ngModel)]="model.lastName" #lastName="ngModel" class="form-control" required />
            </div>
        </div>  
    `
})
export class ChildForm {    
    @Input() model: Person;
      
}

동적 형태의 ~100개의 컨트롤을 사용하면 컨트롤을 암시적으로 포함하면 템플릿 기반의 저그넛이 될 수 있습니다.다음은 유르주이의 기적을 모든 곳에 적용할 것입니다.

export const containerFactory = (container: ControlContainer) => container;

export const controlContainerProvider = [{
  provide: ControlContainer,
  deps: [[new Optional(), new SkipSelf(), ControlContainer]],
  useFactory: containerFactory
}]

@Directive({
  selector: '[ngModel]',
  providers: [controlContainerProvider]
})
export class ControlContainerDirective { }

NgModelGroup이 있는 구성 요소에 controlContainerProvider를 제공합니다.

StackBlizz 예제

양식에는 기본적으로 이름 속성을 설정하는 컨트롤이 필요합니다.다음 지시사항을 사용하여 이 요구사항을 제거하고 이름 특성이 설정된 경우에만 컨트롤을 포함합니다.

import { Directive, ElementRef, HostBinding, OnInit } from '@angular/core';
import { ControlContainer, NgModel } from '@angular/forms';

@Directive({
  selector: '[ngModel]:not([name]):not([ngModelOptions])',
  providers: [{
    provide: ControlContainer,
    useValue: null
  }]
})
export class StandaloneDirective implements OnInit { }

StackBlizz 예제

언급URL : https://stackoverflow.com/questions/39242219/angular2-nested-template-driven-form

반응형