DEV Community

Prasun Chakraborty
Prasun Chakraborty

Posted on

Advanced Form Handling Angular 20 - Dynamic Portfolio Builder and File Upload System

Welcome to Chapter 3 of our comprehensive Angular course! In this chapter, we'll dive deep into the advanced form handling capabilities of our portfolio platform, focusing on the Portfolio Builder with dynamic forms and the File Upload Component.

Recap and Preview

Before we begin, let's see what we have covered till:

  • Chapter 1: Basic portfolio setup with Angular 20 Angular Material and Tailwind CSS
  • Chapter 2: Portfolio Gallary covering Routing, Navigation and Services.

In this chapter we would understand

  • Angular reactive forms and form controls
  • Basic knowledge of File Upload System

By Building our portfolio builder

portfolio builder

You can visit the current version running live on karmakanban.com

Understanding the Portfolio Builder Architecture

The Portfolio Builder implements a hierarchical form structure using Angular's Reactive Forms. The form is organized into logical sections that mirror the portfolio display:

Main Form Structure:

  • Root Form Group: Contains all portfolio sections
  • Hero Data: Personal information and introduction
  • About Data: Detailed personal and professional information
  • Skill Data: Dynamic skill categories with multiple skills
  • Project Data: Dynamic project entries with technologies
  • Contact Data: Contact information and social links

Angular Form Builder: Building Dynamic Forms Like a Pro

Angular Form Builder is one of the most powerful features in Angular for creating complex, dynamic forms. Whether you're building a simple contact form or a sophisticated multi-step wizard like our Portfolio Builder, understanding Form Builder is essential for modern Angular development.
In this comprehensive guide, we'll explore everything about Angular Form Builder, from basic concepts to advanced patterns, using real examples from our Portfolio Builder component. By the end, you'll be able to create any form with confidence!

Types of Angular Forms

Angular provides two approaches to handling forms:

Template-Driven Forms:

  • Forms built using directives in the template,
  • Angular automatically creates form controls
  • Form logic lives in the template
  • Uses ngModel for two-way binding
@Component({
  template: `
    <form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
      <div>
        <label>Name:</label>
        <input type="text" name="name" ngModel required>
      </div>

      <app-file-upload 
        name="avatar"
        ngModel
        accept="image/*"
        required>
      </app-file-upload>

      <button type="submit" [disabled]="!userForm.valid">Submit</button>
    </form>
  `
})
export class TemplateFormComponent {
  onSubmit(form: NgForm) {
    console.log('Form submitted:', form.value);
    // form.value will contain { name: string, avatar: string (file data URL) }
  }
}
Enter fullscreen mode Exit fullscreen mode

Reactive Forms:

  • Forms built programmatically in the component
  • Form logic lives in the component, More control and flexibility
  • Explicit form creation using FormBuilder
  • More explicit control over form state

Our Portfolio Builder uses Reactive Forms because it needs complex validation, dynamic content, and precise control over form behavior.

Form Builder Fundamentals

What is FormBuilder?

FormBuilder is a service provided by Angular that simplifies the creation of reactive forms. It provides methods to create form controls, groups, and arrays with a clean, readable syntax.
Key Methods:

  • group(): Creates a FormGroup, which manages the values and validation status of a collection of FormControl instances.
  • control(): Creates a FormControl, class that represents and manages the state of an individual form input element
  • array(): Creates a FormArray, class that allows for the management of a dynamic collection of form controls. It is particularly useful when the number of form inputs is not fixed and can change at runtime.

Basic FormBuilder Setup and Syntax

To use FormBuilder, you need to:

  1. Import ReactiveFormsModule in your module
  2. Inject FormBuilder in your component
  3. Create form structure using FormBuilder methods
// In your component
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class MyComponent {
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.myForm = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]]
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

FormBuilder.group() creates a FormGroup containing multiple form controls:

Form Validation

Built-in Validators:

  • Validators.required: Field must have a value
  • Validators.email: Must be a valid email format
  • Validators.minLength(n): Minimum character length
  • Validators.maxLength(n): Maximum character length
  • Validators.min(n): Minimum numeric value
  • Validators.max(n): Maximum numeric value
  • Validators.pattern(regex): Custom pattern matching

Validators in Action:

// Array of validators
email: ['', [
  Validators.required,
  Validators.email,
  Validators.minLength(5)
]]
Enter fullscreen mode Exit fullscreen mode

In addition to these we can create custom validators for custom business logic:

// Custom URL validator
static urlValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    if (!control.value) return null;

    const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
    const isValid = urlPattern.test(control.value);

    return isValid ? null : { invalidUrl: { value: control.value } };
  };
}

// Usage in form
contactData: this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  linkedin: ['', CustomValidators.urlValidator()]
})
Enter fullscreen mode Exit fullscreen mode

Nested Form Groups

For complex forms, you can nest FormGroups to organize related fields:

// From our Portfolio Builder
this.portfolioForm = this.fb.group({
  // Root level
  industry: ['', Validators.required],

  // Nested form groups
  heroData: this.fb.group({...}),

  aboutData: this.fb.group({...})
});
Enter fullscreen mode Exit fullscreen mode

Template Binding for Nested Groups:

<form [formGroup]="portfolioForm">
  <!-- Root level -->
  <mat-form-field>
    <input matInput formControlName="industry">
  </mat-form-field>

  <!-- Nested group -->
  <div formGroupName="heroData">
    <mat-form-field>
      <input matInput formControlName="firstName">
    </mat-form-field>
    <mat-form-field>
      <input matInput formControlName="lastName">
    </mat-form-field>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

Form Arrays for Dynamic Content

FormArrays allow you to create dynamic lists of form controls. This is perfect for skills, projects, or any variable-length content.

Creating Form Arrays:

skillData: this.fb.group({
  title: ['My Skills'],
  description: ['', [Validators.required]],
  categories: this.fb.array([this.createSkillCategory()])
}),
Enter fullscreen mode Exit fullscreen mode

Helper Methods for Form Arrays:

// Create a skill category
createSkillCategory(): FormGroup {
  return this.fb.group({
    title: ['', Validators.required],
    icon: ['', Validators.required],
    skills: this.fb.array([
      this.fb.control('', Validators.required),
      this.fb.control('', Validators.required),
      this.fb.control('', Validators.required)
    ])
  });
}
Enter fullscreen mode Exit fullscreen mode

Managing Form Arrays:

// Getter methods for easy access
get categories(): FormArray {
  return this.portfolioForm.get('skillData.categories') as FormArray;
}
// Add new items
addCategory(): void {
  this.categories.push(this.createSkillCategory());
}
// Remove items
removeCategory(index: number): void {
  this.categories.removeAt(index);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)