forms-implementation

Forms Implementation Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "forms-implementation" with this command: npx skills add pluginagentmarketplace/custom-plugin-angular/pluginagentmarketplace-custom-plugin-angular-forms-implementation

Forms Implementation Skill

Quick Start

Template-Driven Forms

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

@Component({ selector: 'app-contact', template: <form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)"> <input [(ngModel)]="model.name" name="name" required minlength="3" /> <input [(ngModel)]="model.email" name="email" email /> <button [disabled]="!contactForm.valid">Submit</button> </form> }) export class ContactComponent { model = { name: '', email: '' };

onSubmit(form: NgForm) { if (form.valid) { console.log('Form submitted:', form.value); } } }

Reactive Forms

import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({ selector: 'app-user-form', template: <form [formGroup]="form" (ngSubmit)="onSubmit()"> <input formControlName="name" placeholder="Name" /> <input formControlName="email" type="email" /> <input formControlName="password" type="password" /> <button [disabled]="form.invalid">Register</button> </form> }) export class UserFormComponent implements OnInit { form!: FormGroup;

constructor(private fb: FormBuilder) {}

ngOnInit() { this.form = this.fb.group({ name: ['', [Validators.required, Validators.minLength(3)]], email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(8)]] }); }

onSubmit() { if (this.form.valid) { console.log(this.form.value); } } }

Form Controls

FormControl

// Create standalone control const nameControl = new FormControl('', Validators.required);

// Get value nameControl.value

// Set value nameControl.setValue('John'); nameControl.patchValue({ name: 'John' });

// Check validity nameControl.valid nameControl.invalid nameControl.errors

// Listen to changes nameControl.valueChanges.subscribe(value => { console.log('Changed:', value); });

FormGroup

const form = new FormGroup({ name: new FormControl('', Validators.required), email: new FormControl('', [Validators.required, Validators.email]), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), zip: new FormControl('') }) });

// Access nested controls form.get('address.street')?.setValue('123 Main St');

// Update multiple values form.patchValue({ name: 'John', email: 'john@example.com' });

FormArray

const form = new FormGroup({ name: new FormControl(''), emails: new FormArray([ new FormControl(''), new FormControl('') ]) });

// Dynamic form array const emailsArray = form.get('emails') as FormArray;

// Add control emailsArray.push(new FormControl(''));

// Remove control emailsArray.removeAt(0);

// Iterate emailsArray.controls.forEach((control, index) => { // ... });

Validation

Built-in Validators

import { Validators } from '@angular/forms';

new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(50), Validators.pattern(/^[a-z]/i), Validators.email, Validators.min(0), Validators.max(100) ])

Custom Validators

// Simple validator function noSpacesValidator(control: AbstractControl): ValidationErrors | null { if (control.value && control.value.includes(' ')) { return { hasSpaces: true }; } return null; }

// Cross-field validator function passwordMatchValidator(group: FormGroup): ValidationErrors | null { const password = group.get('password')?.value; const confirm = group.get('confirmPassword')?.value;

return password === confirm ? null : { passwordMismatch: true }; }

// Usage const form = new FormGroup({ username: new FormControl('', noSpacesValidator), password: new FormControl(''), confirmPassword: new FormControl('') }, passwordMatchValidator);

Async Validators

function emailAvailableValidator(service: UserService): AsyncValidatorFn { return (control: AbstractControl): Observable<ValidationErrors | null> => { if (!control.value) { return of(null); }

return service.checkEmailAvailable(control.value).pipe(
  map(available => available ? null : { emailTaken: true }),
  debounceTime(300),
  first()
);

}; }

// Usage new FormControl('', { validators: Validators.required, asyncValidators: emailAvailableValidator(userService), updateOn: 'blur' });

Form State

const control = form.get('email')!;

// Pristine/Dirty control.pristine // Not modified by user control.dirty // Modified by user

// Touched/Untouched control.untouched // Never focused control.touched // Focused at least once

// Valid/Invalid control.valid control.invalid control.errors control.pending // Async validation in progress

// Status control.status // 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED'

// Value control.value control.getRawValue() // Include disabled controls

Form Display

Showing Errors

<div *ngIf="form.get('email')?.hasError('required')"> Email is required </div>

<div *ngIf="form.get('email')?.hasError('email')"> Invalid email format </div>

<div *ngIf="form.get('email')?.hasError('emailTaken')"> Email already in use </div>

Dynamic Forms

@Component({ template: &#x3C;form [formGroup]="form"> &#x3C;div formArrayName="items"> &#x3C;div *ngFor="let item of items.controls; let i = index"> &#x3C;input [formControlName]="i" /> &#x3C;button (click)="removeItem(i)">Remove&#x3C;/button> &#x3C;/div> &#x3C;/div> &#x3C;button (click)="addItem()">Add Item&#x3C;/button> &#x3C;/form> }) export class DynamicFormComponent { form!: FormGroup;

get items() { return this.form.get('items') as FormArray; }

addItem() { this.items.push(new FormControl('', Validators.required)); }

removeItem(index: number) { this.items.removeAt(index); } }

Advanced Patterns

FormBuilder Groups

this.form = this.fb.group({ basicInfo: this.fb.group({ firstName: ['', Validators.required], lastName: ['', Validators.required], email: ['', [Validators.required, Validators.email]] }), address: this.fb.group({ street: [''], city: [''], zip: [''] }), preferences: this.fb.array([]) });

Directives for Template Forms

<form #form="ngForm"> <input [(ngModel)]="user.name" name="name" required minlength="3" #nameField="ngModelGroup" />

<div *ngIf="nameField.invalid && nameField.touched"> <p *ngIf="nameField.errors?.['required']">Required</p> <p *ngIf="nameField.errors?.['minlength']">Min length 3</p> </div> </form>

Testing Forms

describe('UserFormComponent', () => { let component: UserFormComponent; let fixture: ComponentFixture<UserFormComponent>;

beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [UserFormComponent], imports: [ReactiveFormsModule] }).compileComponents();

fixture = TestBed.createComponent(UserFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();

});

it('should submit valid form', () => { component.form.patchValue({ name: 'John', email: 'john@example.com' });

expect(component.form.valid).toBe(true);

});

it('should show error on invalid email', () => { component.form.get('email')?.setValue('invalid'); expect(component.form.get('email')?.hasError('email')).toBe(true); }); });

Best Practices

  • Reactive Forms for Complex: Use for validation, computed fields

  • Template Forms for Simple: Use for simple, data-binding heavy forms

  • Always validate: Server and client validation

  • Disable submit until valid: Better UX

  • Show errors appropriately: After touched/dirty

  • Handle async validation: Debounce, cancel on unsubscribe

  • Test forms thoroughly: Validation, submission, edge cases

Resources

  • Angular Forms Guide

  • Reactive Forms

  • Form Validation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

angular-material

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

rxjs-implementation

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

state-implementation

No summary provided by upstream source.

Repository SourceNeeds Review