Progress bars are used in dashboards for everything from skill sections to file uploads to multi-step forms. Here's the reusable component pattern I use.

Basic Progress Bar Component

// components/progress-bar/progress-bar.component.ts
import { Component, Input, OnInit, signal } from '@angular/core'
import { CommonModule } from '@angular/common'

export type ProgressColor = 'primary' | 'success' | 'danger' | 'warning' | 'info'

@Component({
  selector: 'app-progress-bar',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div>
      @if (label) {
        <div class="d-flex justify-content-between mb-1">
          <span class="small fw-semibold">{{ label }}</span>
          <span class="small text-muted">{{ animatedValue() }}%</span>
        </div>
      }
      <div class="progress" [style.height]="height + 'px'">
        <div class="progress-bar"
          role="progressbar"
          [class]="colorClass"
          [class.progress-bar-striped]="striped"
          [class.progress-bar-animated]="animated"
          [style.width]="animatedValue() + '%'"
          [attr.aria-valuenow]="animatedValue()"
          aria-valuemin="0"
          aria-valuemax="100">
        </div>
      </div>
    </div>
  `,
  styles: [`
    .progress-bar { transition: width 0.8s ease; }
  `]
})
export class ProgressBarComponent implements OnInit {
  @Input() value = 0
  @Input() label = ''
  @Input() color: ProgressColor = 'primary'
  @Input() striped = false
  @Input() animated = false
  @Input() height = 8

  animatedValue = signal(0)

  get colorClass(): string {
    const map: Record<ProgressColor, string> = {
      primary: 'bg-primary',
      success: 'bg-success',
      danger: 'bg-danger',
      warning: 'bg-warning',
      info: 'bg-info'
    }
    return map[this.color]
  }

  ngOnInit() {
    // Delay to trigger CSS transition on load
    setTimeout(() => this.animatedValue.set(this.value), 100)
  }
}

Skills Section Component

// components/skills/skills.component.ts
import { Component } from '@angular/core'
import { ProgressBarComponent } from '../progress-bar/progress-bar.component'

@Component({
  selector: 'app-skills',
  standalone: true,
  imports: [ProgressBarComponent],
  template: `
    <div class="card border-0 shadow-sm">
      <div class="card-body p-4">
        <h5 class="fw-bold mb-4">Technical Skills</h5>
        <div class="d-flex flex-column gap-3">
          @for (skill of skills; track skill.name) {
            <app-progress-bar
              [value]="skill.level"
              [label]="skill.name"
              [color]="skill.color"
              [height]="10" />
          }
        </div>
      </div>
    </div>
  `
})
export class SkillsComponent {
  skills = [
    { name: 'Angular 21', level: 95, color: 'danger' as const },
    { name: 'Bootstrap 5', level: 92, color: 'primary' as const },
    { name: 'TypeScript', level: 88, color: 'info' as const },
    { name: 'Node.js', level: 80, color: 'success' as const },
    { name: 'UiPath RPA', level: 75, color: 'warning' as const },
  ]
}

Real-Time Progress (File Upload)

// components/upload-progress/upload-progress.component.ts
import { Component, signal } from '@angular/core'
import { CommonModule } from '@angular/common'

@Component({
  selector: 'app-upload-progress',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="card border-0 shadow-sm p-4" style="max-width:400px;">
      <h6 class="fw-bold mb-3">File Upload</h6>

      @if (!isComplete()) {
        <div class="d-flex align-items-center gap-3 mb-3">
          <div class="text-muted small flex-grow-1 text-truncate">{{ fileName }}</div>
          <span class="small fw-bold" style="color:#fd4766;">{{ progress() }}%</span>
        </div>
        <div class="progress mb-3" style="height:6px;">
          <div class="progress-bar progress-bar-striped progress-bar-animated"
            style="background:#fd4766;transition:width 0.3s ease;"
            [style.width]="progress() + '%'">
          </div>
        </div>
        <button class="btn btn-sm btn-outline-secondary" (click)="cancelUpload()">Cancel</button>
      }

      @if (isComplete()) {
        <div class="alert alert-success py-2 mb-0 d-flex align-items-center gap-2">
          <span>✅</span>
          <span class="small">Upload complete!</span>
        </div>
      }

      @if (!isUploading() && !isComplete()) {
        <button class="btn btn-sm text-white" style="background:#fd4766;"
          (click)="startUpload()">Simulate Upload</button>
      }
    </div>
  `
})
export class UploadProgressComponent {
  fileName = 'dashboard-template.zip'
  progress = signal(0)
  isUploading = signal(false)
  isComplete = signal(false)
  private timer?: ReturnType<typeof setInterval>

  startUpload() {
    this.isUploading.set(true)
    this.isComplete.set(false)
    this.progress.set(0)

    this.timer = setInterval(() => {
      this.progress.update(v => {
        if (v >= 100) {
          clearInterval(this.timer)
          this.isComplete.set(true)
          this.isUploading.set(false)
          return 100
        }
        return v + Math.floor(Math.random() * 8) + 2
      })
    }, 150)
  }

  cancelUpload() {
    clearInterval(this.timer)
    this.isUploading.set(false)
    this.progress.set(0)
  }
}

The UploadProgressComponent simulates a real upload — the progress increments in random steps just like a real file upload would. Replace the setInterval with actual HttpClient upload events and it works for real uploads.

Frequently Asked Questions

Set the initial width to 0 and use Angular's transition with a delayed signal update. In ngOnInit: setTimeout(() => this.progress.set(this.targetValue), 100). The CSS transition on .progress-bar handles the animation: .progress-bar { transition: width 0.6s ease; }.
Use a signal for the progress value: progress = signal(0). Update it from your service or interval: setInterval(() => this.progress.update(v => Math.min(v + 10, 100)), 500). The template binds [style.width]='progress() + "%"' and updates automatically.
Add the text inside the progress-bar div: <div class='progress-bar'>{{ progress() }}%</div>. For accessibility add aria-valuenow, aria-valuemin and aria-valuemax attributes bound to the progress value.

Related

Need a Full Angular + Bootstrap Admin Dashboard?

Marvel Angular Dashboard — Angular 21 + Bootstrap 5 with 50+ components and dark mode.

Browse Templates →

Use code FIRST30 for 30% off.