Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author