Introduction to Angular Forms, Routing and Guards

Chathura lakmal
8 min readFeb 6, 2024

--

Welcome to our Angular journey! Explore the power of Angular’s routing, forms, and guards in our comprehensive guide. Learn to navigate seamlessly between components, create dynamic forms, and enforce security with route guards. Whether you’re a beginner or expert, unlock the full potential of Angular with our expert insights. Let’s dive in!

Template-Driven Forms

Template-driven forms are the simplest way to work with forms in Angular. They rely on directives such as ngModel for two-way data binding.

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

@Component({
selector: 'app-template-driven-form',
template: `
<form #myForm="ngForm" (ngSubmit)="submitForm(myForm)">
<label for="username">Username:</label>
<input type="text" id="username" name="username" ngModel required />

<button type="submit">Submit</button>
</form>
`,
})
export class TemplateDrivenFormComponent {
submitForm(form: any) {
// Handle form submission
console.log('Form submitted with data:', form.value);
}
}

Pros:

  • Quick and easy for simple scenarios.
  • Less boilerplate code.

Cons:

  • Limited control over form structure.
  • May become challenging for complex forms.

Reactive Forms:

Reactive forms involve creating form controls and managing them programmatically in the component. This approach provides more control and flexibility.

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

@Component({
selector: 'app-reactive-form',
template: `
<form [formGroup]="myForm" (ngSubmit)="submitForm()">
<label for="username">Username:</label>
<input type="text" id="username" formControlName="username" />

<button type="submit">Submit</button>
</form>
`,
})
export class ReactiveFormComponent implements OnInit {
myForm: FormGroup;

constructor(private fb: FormBuilder) {}

ngOnInit() {
// Initialize the form and its controls
this.myForm = this.fb.group({
username: ['', Validators.required],
});
}

submitForm() {
// Handle form submission
console.log('Form submitted with data:', this.myForm.value);
}
}

Pros:

  • More control and flexibility.
  • Better support for dynamic forms.

Cons:

  • Requires more boilerplate code.
  • Steeper learning curve.

Form Controls Directly in the Component

You can also manage form controls directly in the component without using form groups. This approach is suitable for simpler scenarios.

// Import necessary modules
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';

@Component({
selector: 'app-form-controls-directly',
template: `
<form (ngSubmit)="submitForm()">
<label for="username">Username:</label>
<input type="text" id="username" [formControl]="usernameControl" />

<button type="submit">Submit</button>
</form>
`,
})
export class FormControlsDirectlyComponent {
usernameControl = new FormControl('', Validators.required);

submitForm() {
// Handle form submission
console.log('Form submitted with data:', this.usernameControl.value);
}
}

Pros:

  • Direct control over form controls.
  • Suitable for simple scenarios.

Cons:

  • Limited compared to reactive forms.
  • Less flexibility for dynamic forms.

Comparison Between Template-Driven and Reactive Forms

Examples of Advanced Form Functionalities

Form Arrays

Form arrays allow creating dynamic forms with a variable number of form controls or form groups. Here’s an example of a dynamic form where users can add or remove multiple email fields

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

@Component({
selector: 'app-email-form',
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.css']
})
export class EmailFormComponent {
emailForm: FormGroup;

constructor(private fb: FormBuilder) {
this.emailForm = this.fb.group({
emails: this.fb.array([])
});
}

get emailControls() {
return (this.emailForm.get('emails') as FormArray).controls;
}

addEmailField() {
const emailControl = this.fb.control('', Validators.email);
(this.emailForm.get('emails') as FormArray).push(emailControl);
}

removeEmailField(index: number) {
(this.emailForm.get('emails') as FormArray).removeAt(index);
}

submitForm() {
console.log('Form submitted with data:', this.emailForm.value);
}
}
<!-- email-form.component.html -->
<form [formGroup]="emailForm" (ngSubmit)="submitForm()">
<div formArrayName="emails">
<div *ngFor="let email of emailControls; let i = index">
<input type="email" [formControlName]="i" />
<button type="button" (click)="removeEmailField(i)">Remove</button>
</div>
</div>
<button type="button" (click)="addEmailField()">Add Email</button>
<button type="submit">Submit</button>
</form>

Form Groups Nesting

Form groups nesting allows nesting form controls within form groups to represent complex data structures. Here’s an example of a nested form for capturing user details and address information

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

@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
styleUrls: ['./user-form.component.css']
})
export class UserFormComponent {
userForm: FormGroup;

constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
userDetails: this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
}),
address: this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required],
zip: ['', Validators.required]
})
});
}

submitForm() {
console.log('Form submitted with data:', this.userForm.value);
}
}
<!-- user-form.component.html -->
<form [formGroup]="userForm" (ngSubmit)="submitForm()">
<div formGroupName="userDetails">
<input type="text" formControlName="firstName" placeholder="First Name" />
<input type="text" formControlName="lastName" placeholder="Last Name" />
<input type="email" formControlName="email" placeholder="Email" />
</div>
<div formGroupName="address">
<input type="text" formControlName="street" placeholder="Street" />
<input type="text" formControlName="city" placeholder="City" />
<input type="text" formControlName="zip" placeholder="Zip" />
</div>
<button type="submit">Submit</button>
</form>

Dynamic Form Controls Generation

Dynamic form controls generation allows creating form controls based on user interactions or data-driven conditions. Here’s an example of a dynamic form where form sections are shown based on user choices

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

@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html',
styleUrls: ['./dynamic-form.component.css']
})
export class DynamicFormComponent {
dynamicForm: FormGroup;

constructor(private fb: FormBuilder) {
this.dynamicForm = this.fb.group({
useBillingAddress: [false],
billingAddress: this.fb.group({
street: [''],
city: [''],
zip: ['']
}),
shippingAddress: this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required],
zip: ['', Validators.required]
})
});
}

toggleBillingAddress() {
const useBillingAddress = this.dynamicForm.get('useBillingAddress').value;
const billingAddress = this.dynamicForm.get('billingAddress');

if (useBillingAddress) {
billingAddress.setValidators(Validators.required);
} else {
billingAddress.clearValidators();
}

billingAddress.updateValueAndValidity();
}

submitForm() {
console.log('Form submitted with data:', this.dynamicForm.value);
}
}
<!-- dynamic-form.component.html -->
<form [formGroup]="dynamicForm" (ngSubmit)="submitForm()">
<input type="checkbox" formControlName="useBillingAddress" (change)="toggleBillingAddress()" />
<label>Use Billing Address</label>
<div formGroupName="billingAddress">
<input type="text" formControlName="street" placeholder="Street" />
<input type="text" formControlName="city" placeholder="City" />
<input type="text" formControlName="zip" placeholder="Zip" />
</div>
<div formGroupName="shippingAddress">
<input type="text" formControlName="street" placeholder="Street" />
<input type="text" formControlName="city" placeholder="City" />
<input type="text" formControlName="zip" placeholder="Zip" />
</div>
<button type="submit">Submit</button>
</form>

Now, let’s explore some advanced concepts in Angular: Routing and Services.

Angular Routing

Routing enables navigation between different components in an Angular application. It allows users to move seamlessly between views while maintaining the application state.

Setting Up Routing

To set up routing, define routes in the app-routing.module.ts file and specify the corresponding components.

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}

Navigation

Navigation can be achieved using Angular directives like routerLink or programmatically using the Router service.

<!-- Navigation in HTML -->
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
// Navigation in TypeScript
import { Router } from '@angular/router';

constructor(private router: Router) {}

navigateToAbout() {
this.router.navigate(['/about']);
}

Understanding Lazy Loading

Lazy loading is a strategy in Angular where modules are loaded only when they are needed, rather than loading everything upfront when the application initializes. This approach can significantly improve initial loading times, especially in large applications with multiple modules.

Why Use Lazy Loading?

  • Improved Performance: By loading modules asynchronously, only the necessary code is fetched, reducing the initial load time of the application.
  • Better User Experience: Users can access the initial content faster, leading to a more responsive and engaging experience.
  • Efficient Resource Usage: Modules that are not immediately needed are not loaded, conserving bandwidth and system resources.

How Lazy Loading Works in Angular Routing

  1. Create Feature Modules: Divide your application into feature modules, each representing a distinct section or functionality of your application. These modules should contain their components, services, and other resources.
  2. Set Up Routing for Feature Modules: Define routes for each feature module in their respective routing modules. Specify the paths and components associated with each route within the feature module.
  3. Use loadChildren in AppRoutingModule: Instead of importing feature modules directly into the AppRoutingModule, use the loadChildren property to specify the path to the module file and its module class.

Example: Setting Up Lazy Loading

Let’s consider an e-commerce application with two main feature modules: HomeModule and ProductsModule. We'll set up lazy loading for these modules.

Step 1: Define Routes in AppRoutingModule

// app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{
path: 'home',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
},
// Other routes...
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Step 2: Create Feature Modules

  • HomeModule: Contains components, services, and other resources related to the home page.
  • ProductsModule: Contains components, services, and other resources related to product listings.

Step 3: Define Routes in Feature Modules

// home-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';

const routes: Routes = [
{ path: '', component: HomeComponent }
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }
// products-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductsComponent } from './products.component';

const routes: Routes = [
{ path: '', component: ProductsComponent }
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductsRoutingModule { }

Protecting Routes using Guards

In Angular, route guards are used to protect routes by controlling access based on certain conditions. There are several types of route guards available, including CanActivate, CanActivateChild, CanDeactivate, and CanLoad. Each guard serves a specific purpose and can be applied to individual routes or to child routes.

CanActivate Guard

The CanActivate guard is used to determine if a route can be activated. It's commonly used to enforce authentication or authorization rules before allowing access to a route.

Creating guard

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}

canActivate(): boolean {
if (this.authService.isLoggedIn()) {
return true; // Allow access
} else {
this.router.navigate(['/login']);
return false; // Block access
}
}
}

Applying to Route

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AuthGuard } from './auth.guard';

const routes: Routes = [
{
path: 'home',
component: HomeComponent,
canActivate: [AuthGuard] // Apply guard to this route
},
// Other routes...
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }

Routing Guards in Detail

  1. CanActivate
  • Ensures that a route can be activated based on certain conditions, such as authentication status or user roles.
  • Useful for protecting routes that require authentication or authorization.

2. CanActivateChild

  • Similar to CanActivate but applies to child routes.
  • Useful for guarding child routes within a particular route.

3. CanDeactivate

  • Invoked when a route is about to be deactivated, allowing confirmation or cancellation based on certain conditions.
  • Commonly used for prompting users before leaving a form with unsaved changes.

4. CanLoad

  • Determines if a module can be loaded lazily based on certain conditions.
  • Prevents loading of feature modules if the conditions specified are not met, enhancing application security and performance.

When to Use Guards:

  • Authentication: Ensure users are authenticated before accessing certain routes.
  • Authorization: Control access to routes based on user roles or permissions.
  • Data Integrity: Prevent navigation away from a route if unsaved changes exist.
  • Lazy Loading: Restrict loading of modules based on certain conditions.

Route guards in Angular provide a powerful mechanism for controlling access to routes and enforcing various rules and conditions. By utilizing guards effectively, you can enhance the security, usability, and functionality of your Angular applications.

Until Next Time!

As we conclude our journey through Angular ’s core concepts, I trust you’ve gained invaluable insights into crafting dynamic web applications. Whether you’re just starting or refining your skills, understanding routing, forms, and guards lays a solid foundation.

Stay tuned for our next installment, where we’ll explore advanced Angular techniques, unlocking even more of its potential. Until then, keep coding and embracing the Angular adventure ahead!

Let’s continue this journey together. See you in the next chapter! 👋🚀

part 01:-

https://medium.com/@lakmalc97516/unveiling-angular-a-beginners-guide-to-modern-web-development-13711ebfb051

Part 02 :-

https://medium.com/@lakmalc97516/exploring-angular-components-directives-and-data-binding-590f67140013

--

--