How to build a profile page in Angular
A user profile page combines several features: displaying current user data, an editable form for updating details, avatar upload, and a separate password change section. As the creator of CoreUI with Angular development experience since 2014, I’ve built the profile page components in CoreUI Angular templates that thousands of enterprise developers use as their starting point. The key is splitting the page into focused components — a profile info form and a security settings form — so each has clear responsibility and independent validation. Pre-populating forms from the current user data and handling partial updates cleanly is the most important implementation detail.
Build the profile form pre-populated with current user data.
// profile.component.ts
import { Component, OnInit } from '@angular/core'
import { CommonModule } from '@angular/common'
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms'
import {
CardModule, FormModule, ButtonModule,
GridModule, AvatarModule
} from '@coreui/angular'
import { AuthService } from '../auth/auth.service'
import { UserService } from './user.service'
@Component({
selector: 'app-profile',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, CardModule, FormModule, ButtonModule, GridModule, AvatarModule],
template: `
<c-row>
<c-col md="4">
<c-card class="mb-4 text-center">
<c-card-body class="pt-4">
<c-avatar
size="xl"
[src]="avatarUrl || 'assets/avatar-placeholder.png'"
class="mb-3"
></c-avatar>
<h5>{{ currentUser?.firstName }} {{ currentUser?.lastName }}</h5>
<p class="text-muted">{{ currentUser?.role }}</p>
<label class="btn btn-sm btn-outline-secondary">
Change Photo
<input type="file" hidden accept="image/*" (change)="onAvatarChange($event)" />
</label>
</c-card-body>
</c-card>
</c-col>
<c-col md="8">
<c-card class="mb-4">
<c-card-header>Profile Information</c-card-header>
<c-card-body>
<form [formGroup]="profileForm" (ngSubmit)="saveProfile()">
<c-row>
<c-col sm="6">
<div class="mb-3">
<label cLabel>First Name</label>
<input cFormControl formControlName="firstName" />
</div>
</c-col>
<c-col sm="6">
<div class="mb-3">
<label cLabel>Last Name</label>
<input cFormControl formControlName="lastName" />
</div>
</c-col>
</c-row>
<div class="mb-3">
<label cLabel>Email</label>
<input cFormControl type="email" formControlName="email" />
</div>
<div class="mb-3">
<label cLabel>Bio</label>
<textarea cFormControl formControlName="bio" rows="3"></textarea>
</div>
<p *ngIf="profileSuccess" class="text-success">Profile updated successfully</p>
<button cButton color="primary" type="submit" [disabled]="profileLoading">
{{ profileLoading ? 'Saving...' : 'Save Changes' }}
</button>
</form>
</c-card-body>
</c-card>
<c-card>
<c-card-header>Change Password</c-card-header>
<c-card-body>
<form [formGroup]="passwordForm" (ngSubmit)="changePassword()">
<div class="mb-3">
<label cLabel>Current Password</label>
<input cFormControl type="password" formControlName="currentPassword" />
</div>
<div class="mb-3">
<label cLabel>New Password</label>
<input cFormControl type="password" formControlName="newPassword" />
</div>
<div class="mb-3">
<label cLabel>Confirm New Password</label>
<input cFormControl type="password" formControlName="confirmPassword"
[class.is-invalid]="passwordMismatch" />
<c-form-feedback *ngIf="passwordMismatch">Passwords do not match</c-form-feedback>
</div>
<p *ngIf="passwordError" class="text-danger">{{ passwordError }}</p>
<button cButton color="warning" type="submit" [disabled]="passwordLoading">
Update Password
</button>
</form>
</c-card-body>
</c-card>
</c-col>
</c-row>
`
})
export class ProfileComponent implements OnInit {
currentUser: any = null
avatarUrl = ''
profileLoading = false
profileSuccess = false
passwordLoading = false
passwordError = ''
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
bio: ['']
})
passwordForm = this.fb.group({
currentPassword: ['', Validators.required],
newPassword: ['', [Validators.required, Validators.minLength(8)]],
confirmPassword: ['', Validators.required]
})
get passwordMismatch(): boolean {
const f = this.passwordForm
return f.get('confirmPassword')!.touched &&
f.value.newPassword !== f.value.confirmPassword
}
constructor(
private fb: FormBuilder,
private auth: AuthService,
private userService: UserService
) {}
ngOnInit(): void {
this.currentUser = this.auth.getCurrentUser()
this.profileForm.patchValue({
firstName: this.currentUser.firstName,
lastName: this.currentUser.lastName,
email: this.currentUser.email,
bio: this.currentUser.bio ?? ''
})
this.avatarUrl = this.currentUser.avatarUrl
}
async saveProfile(): Promise<void> {
if (this.profileForm.invalid) return
this.profileLoading = true
await this.userService.updateProfile(this.profileForm.value)
this.profileSuccess = true
this.profileLoading = false
}
async changePassword(): Promise<void> {
if (this.passwordForm.invalid || this.passwordMismatch) return
this.passwordLoading = true
this.passwordError = ''
try {
await this.userService.changePassword(this.passwordForm.value)
this.passwordForm.reset()
} catch (err: any) {
this.passwordError = err.message
}
this.passwordLoading = false
}
onAvatarChange(event: Event): void {
const file = (event.target as HTMLInputElement).files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = (e) => { this.avatarUrl = e.target?.result as string }
reader.readAsDataURL(file)
// Upload the file to the server here
}
}
patchValue populates the form with the current user data without requiring all fields. Keeping the profile update and password change in separate forms avoids confusion and gives each section independent validation and submission state.
Best Practice Note
This is the same profile page structure used in CoreUI Angular Admin Template — separate cards for profile info, avatar, and security settings. For production, add optimistic updates that show the new avatar immediately while the upload is in progress. See how to preview images before upload in Angular for the avatar preview pattern.



