Angular 20 is a robust framework for building dynamic single-page applications (SPAs), and forms are essential for user interactions like registration, login, or data submission. As a beginner, you’ve explored components, templates, directives, routing, and services. Now, it’s time to master Angular forms, which allow you to collect and validate user input efficiently. In this guide, we’ll cover template-driven forms (using FormsModule) and reactive forms (using ReactiveFormsModule), along with form controls, groups, validators, submissions, and custom validation. We’ll build a hands-on project featuring a User Registration Form (reactive) and an Admin Login Form with validation, integrated with your existing User and Admin components, UserService, and routing setup.
We’ll also troubleshoot common issues like the TS2305 error.
This tutorial assumes you have a basic Angular 20 project (e.g., angular-routing-demo from previous modules) and familiarity with components, routing, and services. Let’s dive into Angular forms!
IntroductionForms are a cornerstone of web applications, enabling user input for tasks like signing up, logging in, or updating data. Angular provides two approaches to forms:
Template-Driven Forms (Using FormsModule)Template-driven forms rely on Angular’s template syntax and directives (e.g., ngModel) to manage form data and validation. They’re ideal for simple forms with minimal logic.Key FeaturesExplanation:
Reactive Forms (Using ReactiveFormsModule)Reactive forms are programmatic, defined in the component’s TypeScript code using FormControl, FormGroup, and validators. They offer greater control, testability, and flexibility for complex forms.Key FeaturesExplanation:
Form Controls, Groups, and ValidatorsForm ControlsA FormControl represents a single form input (e.g., text field, checkbox). It tracks the value, validation status, and user interactions.Form GroupsA FormGroup combines multiple FormControl instances, allowing you to manage a form as a single unit.Built-In ValidatorsAngular provides validators like:
Handling Form SubmissionsForm submissions are handled using the (ngSubmit) event, which triggers a method in the component.Template-Driven ExampleReactive ExampleExplanation:
Error Handling & Custom ValidationError HandlingDisplay validation errors in the template using *ngIf and form control properties (e.g., touched, errors).Explanation:Apply it to a FormControl:
Hands-On Project: User Registration Form (Reactive) and Admin Login Form (Template-Driven)Let’s extend your angular-routing-demo project to include a reactive User Registration Form and a template-driven Admin Login Form, using UserService for data management and integrating with routing, directives, and pipes.Step 1: Set Up the ProjectIf not already set up:Step 2: Update app.routes.tsAdd routes for registration and login:Step 3: Update main.tsStep 4: Update AppComponentStep 5: Update UserServiceExplanation:Explanation:Explanation:Step 9: Update UserDetailsComponentStep 10: Update AdminComponentStep 11: Update Child ComponentsStep 12: Update NotFoundComponentStep 13: Update FilterByRolePipeStep 14: Add Custom ValidatorCreate a custom validator for no spaces in the username:Update RegisterComponent to use it:Step 15: Test the Application
Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: main.ts imports App instead of AppComponent.Fix:Steps:
Best Practices for Angular Forms
We’ll also troubleshoot common issues like the TS2305 error.
This tutorial assumes you have a basic Angular 20 project (e.g., angular-routing-demo from previous modules) and familiarity with components, routing, and services. Let’s dive into Angular forms!
IntroductionForms are a cornerstone of web applications, enabling user input for tasks like signing up, logging in, or updating data. Angular provides two approaches to forms:
- Template-Driven Forms: Simple, driven by template directives (e.g., ngModel).
- Reactive Forms: Programmatic, offering more control and flexibility.
- Template-driven forms with FormsModule.
- Reactive forms with ReactiveFormsModule.
- Form controls, groups, and built-in validators.
- Handling form submissions.
- Error handling and custom validation.
- Building a User Registration Form and Admin Login Form.
Template-Driven Forms (Using FormsModule)Template-driven forms rely on Angular’s template syntax and directives (e.g., ngModel) to manage form data and validation. They’re ideal for simple forms with minimal logic.Key Features
- Uses FormsModule for two-way data binding ([(ngModel)]).
- Validation is defined in the template using directives (e.g., required).
- Best for quick, straightforward forms.
typescript
// src/app/components/example/example.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-example',
template: `
form: (ngSubmit)="onSubmit()":
input: [(ngModel)]="username" name="username" required
button: type="submit": Submit
`,
standalone: true,
imports: [FormsModule]
})
export class ExampleComponent {
username = '';
onSubmit() {
console.log('Username:', this.username);
}
}
- FormsModule: Enables ngModel and form directives.
- [(ngModel)]: Binds the input to the username property.
- required: A built-in HTML validator enforced by Angular.
Reactive Forms (Using ReactiveFormsModule)Reactive forms are programmatic, defined in the component’s TypeScript code using FormControl, FormGroup, and validators. They offer greater control, testability, and flexibility for complex forms.Key Features
- Uses ReactiveFormsModule for form creation.
- Form structure and validation are defined in the component.
- Ideal for dynamic forms and complex validation.
typescript
// src/app/components/example/example.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-example',
template: `
form: [formGroup]="form" (ngSubmit)="onSubmit()":
input: formControlName="username"
button: type="submit": Submit
`,
standalone: true,
imports: [ReactiveFormsModule]
})
export class ExampleComponent {
form = new FormGroup({
username: new FormControl('', Validators.required)
});
onSubmit() {
console.log('Form Value:', this.form.value);
}
}
- ReactiveFormsModule: Provides reactive form APIs.
- FormGroup: Groups multiple form controls.
- FormControl: Manages a single input field with validators.
- Validators.required: Ensures the field is not empty.
Form Controls, Groups, and ValidatorsForm ControlsA FormControl represents a single form input (e.g., text field, checkbox). It tracks the value, validation status, and user interactions.
typescript
new FormControl('', Validators.required)
typescript
form = new FormGroup({
username: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email])
});
- Validators.required: Ensures the field is not empty.
- Validators.email: Validates email format.
- Validators.minLength(n): Ensures minimum length.
- Validators.pattern(regex): Matches a regular expression.
Handling Form SubmissionsForm submissions are handled using the (ngSubmit) event, which triggers a method in the component.Template-Driven Example
typescript
onSubmit() {
console.log('Form submitted:', this.model);
}
typescript
onSubmit() {
if (this.form.valid) {
console.log('Form submitted:', this.form.value);
}
}
- Check form.valid in reactive forms to ensure all validations pass.
- Use form.value to access form data.
Error Handling & Custom ValidationError HandlingDisplay validation errors in the template using *ngIf and form control properties (e.g., touched, errors).
typescript
// Template
div: *ngIf="form.get('username')?.hasError('required') && form.get('username')?.touched":
Username is required
- hasError('required'): Checks for specific validation errors.
- touched: Ensures errors show only after user interaction.
typescript
function noSpacesValidator(control: FormControl): { [key: string]: any } | null {
return control.value?.includes(' ') ? { noSpaces: true } : null;
}
typescript
username: new FormControl('', [Validators.required, noSpacesValidator])
Hands-On Project: User Registration Form (Reactive) and Admin Login Form (Template-Driven)Let’s extend your angular-routing-demo project to include a reactive User Registration Form and a template-driven Admin Login Form, using UserService for data management and integrating with routing, directives, and pipes.Step 1: Set Up the ProjectIf not already set up:
bash
ng new angular-routing-demo
cd angular-routing-demo
ng g component components/user
ng g component components/admin
ng g component components/user-details
ng g component components/not-found
ng g component components/admin-dashboard
ng g component components/admin-settings
ng g component components/register
ng g component components/login
ng g pipe pipes/filter-by-role
ng g service services/user
typescript
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { UserComponent } from './components/user/user.component';
import { AdminComponent } from './components/admin/admin.component';
import { UserDetailsComponent } from './components/user-details/user-details.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AdminDashboardComponent } from './components/admin-dashboard/admin-dashboard.component';
import { AdminSettingsComponent } from './components/admin-settings/admin-settings.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
export const routes: Routes = [
{ path: 'user', component: UserComponent },
{ path: 'user/:id', component: UserDetailsComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{
path: 'admin',
component: AdminComponent,
children: [
{ path: 'dashboard', component: AdminDashboardComponent },
{ path: 'settings', component: AdminSettingsComponent },
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' }
]
},
{ path: '', redirectTo: '/user', pathMatch: 'full' },
{ path: '**', component: NotFoundComponent }
];
typescript
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)]
}).catch(err => console.error(err));
typescript
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd, RouterOutlet, RouterLink } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
standalone: true,
imports: [RouterOutlet, RouterLink]
})
export class AppComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
console.log('Navigation started:', event.url);
} else if (event instanceof NavigationEnd) {
console.log('Navigation ended:', event.url);
}
});
}
}
typescript
// src/app/app.component.html
nav:
a: routerLink="/user" routerLinkActive="active": User Dashboard
a: routerLink="/register" routerLinkActive="active": Register
a: routerLink="/login" routerLinkActive="active": Login
a: routerLink="/admin" routerLinkActive="active": Admin Panel
router-outlet
css
// src/app/app.component.css
nav { margin: 20px; }
a { margin-right: 10px; text-decoration: none; }
.active { font-weight: bold; color: #007bff; }
typescript
// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UserService {
private users = [
{ id: 1, name: 'Alice', role: 'admin', email: 'alice@example.com' },
{ id: 2, name: 'Bob', role: 'user', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', role: 'admin', email: 'charlie@example.com' },
{ id: 4, name: 'David', role: 'user', email: 'david@example.com' }
];
private admins = [
{ username: 'admin1', password: 'admin123' },
{ username: 'admin2', password: 'admin456' }
];
getUsers() {
return this.users;
}
getUserById(id: number) {
return this.users.find(user => user.id === id);
}
addUser(user: { id: number; name: string; role: string; email: string }) {
this.users.push(user);
}
validateAdmin(username: string, password: string) {
return this.admins.some(admin => admin.username === username && admin.password === password);
}
}
- Added email to users for the registration form.
- Added admins array and validateAdmin() for login validation.
typescript
// src/app/components/register/register.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css'],
standalone: true,
imports: [ReactiveFormsModule, RouterLink]
})
export class RegisterComponent {
registerForm = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(3)]),
email: new FormControl('', [Validators.required, Validators.email]),
role: new FormControl('user', [Validators.required])
});
constructor(private userService: UserService, private router: Router) {}
onSubmit() {
if (this.registerForm.valid) {
const formValue = this.registerForm.value;
this.userService.addUser({
id: this.userService.getUsers().length + 1,
name: formValue.name || '',
email: formValue.email || '',
role: formValue.role || 'user'
});
this.registerForm.reset();
this.router.navigate(['/user']);
}
}
}
typescript
// src/app/components/register/register.component.html
h2: User Registration
form: [formGroup]="registerForm" (ngSubmit)="onSubmit()":
div:
label: Name:
input: formControlName="name"
div: *ngIf="registerForm.get('name')?.hasError('required') && registerForm.get('name')?.touched":
Name is required
div: *ngIf="registerForm.get('name')?.hasError('minlength') && registerForm.get('name')?.touched":
Name must be at least 3 characters
div:
label: Email:
input: formControlName="email"
div: *ngIf="registerForm.get('email')?.hasError('required') && registerForm.get('email')?.touched":
Email is required
div: *ngIf="registerForm.get('email')?.hasError('email') && registerForm.get('email')?.touched":
Invalid email format
div:
label: Role:
select: formControlName="role":
option: value="user": User
option: value="admin": Admin
button: type="submit" [disabled]="registerForm.invalid": Register
p: Back to User Dashboard
css
// src/app/components/register/register.component.css
form { margin: 20px; padding: 20px; border: 1px solid #ccc; }
input, select { padding: 5px; margin: 5px; width: 200px; }
div { margin-bottom: 10px; }
.error { color: red; font-size: 0.9em; }
- Uses ReactiveFormsModule for a programmatic form.
- FormGroup with FormControl instances for name, email, and role.
- Validators: required, minLength, email.
- Error messages display only when fields are touched.
- Submits to UserService and navigates to /user.
typescript
// src/app/components/login/login.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
standalone: true,
imports: [FormsModule, RouterLink]
})
export class LoginComponent {
username = '';
password = '';
errorMessage = '';
constructor(private userService: UserService, private router: Router) {}
onSubmit() {
if (this.userService.validateAdmin(this.username, this.password)) {
this.router.navigate(['/admin']);
} else {
this.errorMessage = 'Invalid username or password';
}
}
}
typescript
// src/app/components/login/login.component.html
h2: Admin Login
form: (ngSubmit)="onSubmit()":
div:
label: Username:
input: [(ngModel)]="username" name="username" required
div: *ngIf="form.username?.errors?.['required'] && form.username?.touched": Username is required
div:
label: Password:
input: [(ngModel)]="password" name="password" type="password" required #passwordInput
div: *ngIf="form.password?.errors?.['required'] && form.password?.touched": Password is required
div: *ngIf="errorMessage" class="error": {{ errorMessage }}
button: type="submit" [disabled]="!form.valid": Login
p: Back to User Dashboard
css
// src/app/components/login/login.component.css
form { margin: 20px; padding: 20px; border: 1px solid #ccc; }
input { padding: 5px; margin: 5px; width: 200px; }
div { margin-bottom: 10px; }
.error { color: red; font-size: 0.9em; }
- Uses FormsModule for a template-driven form.
- ngModel binds username and password.
- required validator ensures fields are filled.
- #passwordInput is a template reference variable (not used here but included for reference).
- Validates credentials via UserService and navigates to /admin on success.
typescript
// src/app/components/user/user.component.ts
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
standalone: true,
imports: [FormsModule, RouterLink]
})
export class UserComponent implements OnInit {
searchQuery = '';
users: { id: number; name: string; role: string; email: string }[] = [];
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {}
ngOnInit() {
this.users = this.userService.getUsers();
this.route.queryParamMap.subscribe(params => {
this.searchQuery = params.get('search') || '';
});
}
updateQuery() {
this.router.navigate(['/user'], { queryParams: { search: this.searchQuery } });
}
}
typescript
// src/app/components/user/user.component.html
h2: User Dashboard
div:
label: Search users:
input: [(ngModel)]="searchQuery" placeholder="Search users"
button: (click)="updateQuery()": Search
p: Search Query: {{ searchQuery }}
ul:
li: *ngFor="let user of users":
a: routerLink="/user/{{ user.id }}": {{ user.name }} ({{ user.role }})
css
// src/app/components/user/user.component.css
ul { list-style-type: none; padding: 0; }
li { padding: 10px; margin: 5px 0; border: 1px solid #ccc; }
typescript
// src/app/components/user-details/user-details.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-user-details',
templateUrl: './user-details.component.html',
standalone: true,
imports: [RouterLink]
})
export class UserDetailsComponent implements OnInit {
user: { id: number; name: string; role: string; email: string } | undefined;
constructor(
private route: ActivatedRoute,
private userService: UserService
) {}
ngOnInit() {
this.route.paramMap.subscribe(params => {
const id = Number(params.get('id'));
this.user = this.userService.getUserById(id);
});
}
}
typescript
// src/app/components/user-details/user-details.component.html
h2: User Details
div: *ngIf="user; else noUser":
p: Name: {{ user.name }}
p: Email: {{ user.email }}
p: Role: {{ user.role | uppercase }}
p: else noUser: User not found
p: Back to User Dashboard
typescript
// src/app/components/admin/admin.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterOutlet, RouterLink } from '@angular/router';
import { FilterByRolePipe } from '../../pipes/filter-by-role.pipe';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.css'],
standalone: true,
imports: [FormsModule, RouterOutlet, RouterLink, FilterByRolePipe]
})
export class AdminComponent implements OnInit, OnDestroy {
users: { id: number; name: string; role: string; email: string }[] = [];
filterRole = 'admin';
isHighlighted = false;
constructor(private userService: UserService) {}
ngOnInit() {
console.log('AdminComponent: Initialized');
this.users = this.userService.getUsers();
}
ngOnDestroy() {
console.log('AdminComponent: Destroyed');
}
toggleHighlight() {
this.isHighlighted = !this.isHighlighted;
}
setFilterRole(role: string) {
this.filterRole = role;
}
logSearch(value: string) {
console.log('Search:', value);
}
}
typescript
// src/app/components/admin/admin.component.html
h2: Admin Panel
nav:
a: routerLink="dashboard" routerLinkActive="active": Dashboard
a: routerLink="settings" routerLinkActive="active": Settings
router-outlet
h3: Admin Users List
div:
label: Filter by Role:
select: [(ngModel)]="filterRole" (change)="setFilterRole(filterRole)":
option: value="all": All
option: value="admin": Admin
option: value="user": User
button: (click)="toggleHighlight()": Toggle Highlight
ul:
li: *ngFor="let user of users | filterByRole:filterRole" [ngClass]="{'highlight': isHighlighted && user.role === 'admin'}":
{{ user.name | uppercase }} ({{ user.role }})
input: #searchInput placeholder="Search users"
button: (click)="logSearch(searchInput.value)": Search
css
// src/app/components/admin/admin.component.css
ul { list-style-type: none; padding: 0; }
li { padding: 10px; margin: 5px 0; border: 1px solid #ccc; }
.highlight { background-color: #e0f7fa; }
nav { margin: 10px 0; }
a { margin-right: 10px; text-decoration: none; }
.active { font-weight: bold; color: #007bff; }
typescript
// src/app/components/admin-dashboard/admin-dashboard.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-admin-dashboard',
template: `
h3: Admin Dashboard
p: Welcome to the Admin Dashboard!
`,
standalone: true
})
export class AdminDashboardComponent {}
typescript
// src/app/components/admin-settings/admin-settings.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-admin-settings',
template: `
h3: Admin Settings
p: Configure settings here.
`,
standalone: true
})
export class AdminSettingsComponent {}
typescript
// src/app/components/not-found/not-found.component.ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-not-found',
template: `
h2: 404 - Page Not Found
p: Go back to User Dashboard
`,
standalone: true,
imports: [RouterLink]
})
export class NotFoundComponent {}
typescript
// src/app/pipes/filter-by-role.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterByRole',
standalone: true
})
export class FilterByRolePipe implements PipeTransform {
transform(users: any[], role: string): any[] {
if (!users || role === 'all') return users;
return users.filter(user => user.role === role);
}
}
typescript
// src/app/validators/no-spaces.validator.ts
import { AbstractControl, ValidatorFn } from '@angular/forms';
export function noSpacesValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
const hasSpaces = control.value?.includes(' ');
return hasSpaces ? { noSpaces: true } : null;
};
}
typescript
// src/app/components/register/register.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
import { noSpacesValidator } from '../../validators/no-spaces.validator';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css'],
standalone: true,
imports: [ReactiveFormsModule, RouterLink]
})
export class RegisterComponent {
registerForm = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(3), noSpacesValidator()]),
email: new FormControl('', [Validators.required, Validators.email]),
role: new FormControl('user', [Validators.required])
});
constructor(private userService: UserService, private router: Router) {}
onSubmit() {
if (this.registerForm.valid) {
const formValue = this.registerForm.value;
this.userService.addUser({
id: this.userService.getUsers().length + 1,
name: formValue.name || '',
email: formValue.email || '',
role: formValue.role || 'user'
});
this.registerForm.reset();
this.router.navigate(['/user']);
}
}
}
typescript
// src/app/components/register/register.component.html
h2: User Registration
form: [formGroup]="registerForm" (ngSubmit)="onSubmit()":
div:
label: Name:
input: formControlName="name"
div: *ngIf="registerForm.get('name')?.hasError('required') && registerForm.get('name')?.touched":
Name is required
div: *ngIf="registerForm.get('name')?.hasError('minlength') && registerForm.get('name')?.touched":
Name must be at least 3 characters
div: *ngIf="registerForm.get('name')?.hasError('noSpaces') && registerForm.get('name')?.touched":
Name cannot contain spaces
div:
label: Email:
input: formControlName="email"
div: *ngIf="registerForm.get('email')?.hasError('required') && registerForm.get('email')?.touched":
Email is required
div: *ngIf="registerForm.get('email')?.hasError('email') && registerForm.get('email')?.touched":
Invalid email format
div:
label: Role:
select: formControlName="role":
option: value="user": User
option: value="admin": Admin
button: type="submit" [disabled]="registerForm.invalid": Register
p: Back to User Dashboard
- Run ng serve and visit http://localhost:4200.
- Test features:
- Register (/register):
- Fill out the form (e.g., name: “Eve”, email: “eve@example.com”, role: “user”).
- Try invalid inputs (e.g., empty fields, spaces in name) to see error messages.
- Submit to add the user and navigate to /user.
- Login (/login):
- Use admin1/admin123 to log in and navigate to /admin.
- Try invalid credentials to see the error message.
- User Dashboard (/user):
- Verify new users appear in the list.
- Use the search query and navigate to user details.
- Admin Panel (/admin):
- Filter users and toggle highlighting.
- Navigate to child routes (/admin/dashboard, /admin/settings).
- User Details (/user/:id):
- View user details or “User not found”.
- 404 Page (/invalid):
- Confirm the Not Found page appears.
- Register (/register):
Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: main.ts imports App instead of AppComponent.Fix:
typescript
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)]
}).catch(err => console.error(err));
- Verify app.component.ts exports AppComponent.
- Check the file path.
- Run ng serve.
- Import FormsModule for template-driven forms or ReactiveFormsModule for reactive forms in the component’s imports.
- Use *ngIf="form.get('field')?.hasError('error') && form.get('field')?.touched".
Best Practices for Angular Forms
- Choose the Right Form Type: Use template-driven for simple forms, reactive for complex ones.
- Validate Early: Show errors only after touched to avoid overwhelming users.
- Use Custom Validators: Encapsulate complex rules in reusable validators.
- Secure Forms: Sanitize inputs and use server-side validation for critical data.
- Test Forms: Write unit tests for form logic using Jasmine.
0 comments:
Post a Comment