How to build a checkout page in Angular
Building a checkout page is a critical task in e-commerce that requires a perfect balance of user experience and data integrity.
As the creator of CoreUI and with over 25 years of software development experience, I’ve designed dozens of checkout flows that handle complex validation and state management.
In Angular, the most efficient way to manage a multi-field checkout process is by utilizing Reactive Forms combined with a modular component architecture.
This approach ensures your form is scalable, easy to test, and provides immediate feedback to the user.
Use Angular Reactive Forms to build a structured, validated checkout flow that integrates seamlessly with CoreUI components.
1. Defining the Form Model
The first step is to create a robust form structure using FormBuilder. We will group fields into logical sections like personal information, shipping address, and payment details.
import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'
import { CardModule, GridModule, FormModule, ButtonModule, ListGroupModule } from '@coreui/angular'
@Component({
selector: 'app-checkout',
standalone: true,
imports: [ReactiveFormsModule, CardModule, GridModule, FormModule, ButtonModule, ListGroupModule],
templateUrl: './checkout.component.html'
})
export class CheckoutComponent implements OnInit {
checkoutForm!: FormGroup
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.checkoutForm = this.fb.group({
personalInfo: this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(2)]],
lastName: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]]
}),
shipping: this.fb.group({
address: ['', [Validators.required]],
city: ['', [Validators.required]],
zip: ['', [Validators.required, Validators.pattern('^[0-9]{5}$')]]
}),
payment: this.fb.group({
method: ['cc', [Validators.required]],
cardName: ['', [Validators.required]],
cardNumber: ['', [Validators.required, Validators.pattern('^[0-9]{16}$')]],
expiry: ['', [Validators.required]],
cvv: ['', [Validators.required, Validators.maxLength(3)]]
})
})
}
onSubmit() {
if (this.checkoutForm.valid) {
console.log('Order Data:', this.checkoutForm.value)
}
}
}
2. Implementing the Layout with CoreUI Cards
Using CoreUI Cards, we can separate the checkout process into distinct visual sections. This reduces cognitive load for the customer.
<form [formGroup]="checkoutForm" (ngSubmit)="onSubmit()">
<c-row>
<c-col [md]="8">
<!-- Shipping Section -->
<c-card class="mb-4">
<c-card-header>
<strong>Shipping Information</strong>
</c-card-header>
<c-card-body formGroupName="shipping">
<c-row class="mb-3">
<c-col [md]="12">
<label cLabel for="address">Street Address</label>
<input cFormControl id="address" formControlName="address" placeholder="123 Main St" />
</c-col>
</c-row>
<c-row>
<c-col [md]="6">
<label cLabel for="city">City</label>
<input cFormControl id="city" formControlName="city" />
</c-col>
<c-col [md]="6">
<label cLabel for="zip">Zip Code</label>
<input cFormControl id="zip" formControlName="zip" />
</c-col>
</c-row>
</c-card-body>
</c-card>
</c-col>
</c-row>
</form>
3. Adding Payment Method Selection
For payment selection, we use CoreUI Checks and Radios to allow users to toggle between Credit Card, PayPal, or other methods.
<c-card class="mb-4">
<c-card-header>
<strong>Payment Method</strong>
</c-card-header>
<c-card-body formGroupName="payment">
<c-form-check>
<input cFormCheckInput type="radio" formControlName="method" id="cc" value="cc" />
<label cFormCheckLabel for="cc">Credit Card</label>
</c-form-check>
<c-form-check>
<input cFormCheckInput type="radio" formControlName="method" id="paypal" value="paypal" />
<label cFormCheckLabel for="paypal">PayPal</label>
</c-form-check>
<div class="mt-4">
<label cLabel for="cardName">Name on Card</label>
<input cFormControl id="cardName" formControlName="cardName" />
<c-row class="mt-3">
<c-col [md]="8">
<label cLabel for="cardNumber">Card Number</label>
<input cFormControl id="cardNumber" formControlName="cardNumber" />
</c-col>
<c-col [md]="4">
<label cLabel for="cvv">CVV</label>
<input cFormControl id="cvv" formControlName="cvv" />
</c-col>
</c-row>
</div>
</c-card-body>
</c-card>
4. Building the Order Summary
An order summary is essential for trust. We can use a CoreUI List Group to display items and calculate the total.
<c-col [md]="4">
<c-card>
<c-card-header>
<strong>Order Summary</strong>
</c-card-header>
<c-card-body>
<ul cListGroup flush>
<li cListGroupItem class="d-flex justify-content-between align-items-center">
Product A <span>$49.99</span>
</li>
<li cListGroupItem class="d-flex justify-content-between align-items-center">
Product B <span>$25.00</span>
</li>
<li cListGroupItem class="d-flex justify-content-between align-items-center border-top mt-2">
<strong>Total</strong>
<strong>$74.99</strong>
</li>
</ul>
<button cButton color="primary" class="w-100 mt-3" type="submit" [disabled]="!checkoutForm.valid">
Complete Purchase
</button>
</c-card-body>
</c-card>
</c-col>
5. Integrating Form Validation Feedback
Angular Reactive Forms provide state classes that we can map to CoreUI Validation styles to show real-time errors.
// Add a helper method to check field status
isFieldInvalid(section: string, field: string): boolean {
const control = this.checkoutForm.get(`${section}.${field}`)
return !!(control && control.invalid && (control.dirty || control.touched))
}
<input
cFormControl
id="email"
formControlName="email"
[valid]="!isFieldInvalid('personalInfo', 'email')"
/>
<c-form-feedback *ngIf="isFieldInvalid('personalInfo', 'email')" [valid]="false">
Please provide a valid email address.
</c-form-feedback>
6. Optional: Multi-step Checkout with Stepper (PRO)
For longer checkouts, the CoreUI Stepper improves the flow by breaking the form into steps (e.g., Shipping -> Payment -> Review). This is a PRO component available in the @coreui/angular-pro package.
<c-stepper>
<c-stepper-header>
<c-stepper-item>Shipping</c-stepper-item>
<c-stepper-item>Payment</c-stepper-item>
<c-stepper-item>Confirm</c-stepper-item>
</c-stepper-header>
<c-stepper-content>
<!-- Step content goes here -->
</c-stepper-content>
</c-stepper>
Explanation
In this implementation, we use Angular’s FormBuilder to create a nested FormGroup. This allows us to separate concerns into personalInfo, shipping, and payment. The [formGroup] directive in the template binds our logic to the UI. We use CoreUI’s grid system (c-row, c-col) to create a responsive two-column layout where the form sits on the left and the order summary stays pinned to the right (on larger screens). Each input uses the cFormControl directive, which automatically applies professional styling and integrates with Angular’s validation state. The “Complete Purchase” button is bound to the form’s validity, preventing submission if requirements aren’t met.
Best Practice Note:
Always ensure you handle sensitive payment data securely. In a production environment, you should never send raw credit card details to your own server; instead, use a provider like Stripe to tokenize the data. For high-conversion checkouts, we recommend using the CoreUI Angular Dashboard Template which includes pre-built eCommerce pages. If you are just starting your project, you might want to check how to create a new angular project first. Additionally, make sure to format numbers as currency in your summary for a professional finish.



