ng-forms

Angular Forms


Motivation

<label>
  Name: <input [(ngModel)]='name'>
</label>
<input [(ngModel)]='port' type='number'>
<button (click)='addEnvVar()'>+</button>
@for (envVar of envVars(); track $index) {
  <input [(ngModel)]='envVar.name'>
  <input [(ngModel)]='envVar.value'>
}
<button (click)='submit()'>Deploy</button>
  • Validierung?
  • Dynamisches Form?
  • Komplexeres Form?

FormControl

name = new FormControl('');
<label>
  Name: <input [formControl]='name'>
</label>
<p>Deploying {{ name.value }}</p>
  • ein Input
  • Two-Way-Binding

FormGroup

deployForm = new FormGroup({
  'name': new FormControl(''),
  'port': new FormControl(80)
});
<form [formGroup]='deployForm'>
  <label for='name'>Name: </label>
  <input id='name' formControlName='name'>

  <label>
    Port: <input formControlName='port' type='number'>
  </label>
</form>
<p>{{ deployForm.value | json }}</p>
<!-- { 'name': 'backend', 'port': 80 } -->
  • mehrere Inputs

Submit

<form [formGroup]='deployForm' (ngSubmit)='onSubmit()'>
  ...
  <button type='submit' [disabled]='!deployForm.valid'>
    Deploy
  </button>
</form>
onSubmit() {
  console.log(this.deployForm.value);
}

Setting/Updating

setValue() {
  this.deployForm.setValue({
    'name': '',
    'port': 80
  });
}

Setzt alle Werte

updateValue() {
  this.deployForm.patchValue({
    'name': ''
  });
}

FormBuilder

deployForm = new FormGroup({
  'name': new FormControl(''),
  'port': new FormControl(80)
});
formBuilder = inject(FormBuilder);

deployForm = this.formBuilder.group({
  'name': [''],
  'port': [80]
});

form.reset() => null

formBuilder = inject(FormBuilder).nonNullable;

form.reset() => default


FormArray

deployForm = this.formBuilder.group({
  ...
  'envVars': this.formBuilder.array([
    this.formBuilder.group({
      'name': ['application-name'],
      'value': ['']
    })
  ])
});
<form [formGroup]='deployForm' (ngSubmit)='onSubmit()'>
  <div formArrayName='envVars'>
    @for (envVar of deployForm.controls.envVars.controls; track $index) {
      <div [formGroupName]='$index'>
        <div>
          <label for='env-name'>Name: </label>
          <input id='env-name' formControlName='name'>
          <label for='env-value'>Value: </label>
          <input id='env-value' formControlName='value'>
        </div>
      </div>
    }
  </div>

Hinzufügen


addEnvVar() {
  (this.deployForm.controls.envVars as FormArray).push(
    this.formBuilder.group({
      'name': [''],
      'value': ['']
    })
  );
}

Bei leeren Arrays Typstellvertreter hilfreich

'envVars': this.formBuilder.array
  <FormGroup<{ name: AbstractControl, value: AbstractControl }>>
  ([], {validators: this.unique()})

Validation

deployForm = this.formBuilder.group({
  'name': ['', Validators.required],
  'port': [80,
    [ // synchronous Validators
      Validators.required,
      Validators.min(1),
      Validators.max(65535),
      this.validPort()
    ],
    Validators.customAsync // asynchronous Validators
  ],
  'envVars': this.formBuilder.array([], {validators: this.unique()})
}, {validators: this.globalValidation()});
validPort(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const port = control.value;
    return [0, 443, 5432].find(p => p == port) 
        ? {'Invalid port': port} // validation error 
        : null; // no error
  };
}

CSS


.ng-touched:not(form,div) {
    border-left: 5px solid blue;
}

Element war einmal im Focus


.ng-dirty:not(form) {
    border-right: 5px solid green;
}

Element-value wurde verändert


.ng-invalid.ng-touched:not(form), 
  .ng-invalid.ng-dirty:not(form) {
    border-left: 5px solid red;
}

Validator liefert Error


Error Messages

@if (deployForm.controls.port.invalid &&
  (deployForm.controls.port.touched || deployForm.controls.port.dirty)) {
  Error: 
  @if (deployForm.controls.port.errors?.['Invalid port']) {
    Forbidden port
  }
  @if (deployForm.controls.port.errors?.['min']) {
    Port must be positive
  }
}