Working with forms in Angular often involves repeating the same steps:
- Collecting form data
- Making an API call
- Managing loading states
- Handling success and error responses
- Disabling the submit button during processing
If you find yourself duplicating this logic across multiple components, consider a more efficient approach.
Typically, a form submission in Angular might look like this:
onSubmit() {
this.isSubmitting = true;
const payload = this.form.value;
this.api.saveUser(payload).pipe(
finalize(() => this.isSubmitting = false)
).subscribe({
next: res => console.log('Success', res),
error: err => console.error('Error', err)
});
}
While functional, this pattern can become repetitive and more challenging to maintain as your application grows.
Introducing a Reusable Base Component
To streamline this process, we can create a base component that encapsulates the common submission logic:
// base-submit.component.ts
export abstract class BaseSubmitComponent {
isSubmitting = false;
// Method to create the payload
abstract createPayload(): T;
// Method to handle successful submission
abstract onSubmitSuccess(response: any): void;
protected submit(apiFn: (payload: T) => Observable) {
this.isSubmitting = true;
const payload = this.createPayload();
apiFn(payload).pipe(
finalize(() => this.isSubmitting = false)
).subscribe({
next: res => this.onSubmitSuccess(res),
error: err => this.handleError(err)
});
}
protected handleError(err: any) {
console.error('Submit error:', err);
// Implement your error handling logic here
}
}
With this base component, individual form components can focus on their specific logic:
export class UserFormComponent extends BaseSubmitComponent {
form = this.fb.group({
name: [''],
email: ['']
});
constructor(private fb: FormBuilder, private api: ApiService) {
super();
}
createPayload(): UserPayload {
return this.form.value;
}
onSubmitSuccess(response: any) {
console.log('User saved successfully:', response);
// Additional success handling
}
onSubmit() {
this.submit(payload => this.api.saveUser(payload));
}
}
Advantages of This Approach
- Consistency: Ensures uniform handling of submissions across components.
- Maintainability: Centralizes common logic, making updates easier.
- Clarity: Keeps individual components focused on their unique responsibilities.
- Extensibility: Allows for easy addition of features like validation checks or pre-submission hooks.
Enhancements to Consider
- Shared UI Components: Create reusable components for buttons or form fields that integrate with the submission state.
- Centralized Error Handling: Implement a global service for displaying error messages or notifications.
- Form Validation: Incorporate validation logic to prevent invalid submissions.
Edge Cases to Handle
When implementing this form submission pattern, consider these important scenarios:
-
Custom Error Handling: Allow components to implement their error handling via an
onSubmitError
method. - Pre-submission Validation: Add validation checks before API calls to prevent invalid submissions.
- Cancellable Requests: Implement a way to cancel ongoing submissions if users navigate away.
- Form Reset Logic: Add standardized methods to reset forms after successful submissions.
- Submission Throttling: Prevent duplicate submissions from rapid clicking.