如何避免Angular应用中用户连续重复提交表单?
Great question! Preventing duplicate form submissions is critical, especially once your app is live and exposed to abuse like the script you shared. Let’s break down several practical solutions tailored to your Angular + Firebase setup:
1. Disable the Submit Button During Request
This is the most straightforward approach for improving user experience and blocking accidental (or script-driven) repeated clicks. We’ll add a state flag to track when a submission is in progress, then use it to disable the button and update its text.
Update your component code:
import { Component, OnInit } from '@angular/core'; import { NgForm } from '@angular/forms'; import { AngularFireDatabase } from '@angular/fire/compat/database'; export class InsertTODO implements OnInit { // Track submission state isSubmitting = false; constructor(private angularFire: AngularFireDatabase) { } ngOnInit() { } form_submit(f: NgForm) { // Exit if already submitting to prevent duplicate calls if (this.isSubmitting) return; this.isSubmitting = true; this.angularFire.list('todos').push({ name: f.controls.name.value }).then((t: any) => { console.log('recorded data ' + t.key); f.controls.name.setValue(''); }, (e: any) => { console.log(e.message); }).finally(() => { // Re-enable button regardless of success/failure this.isSubmitting = false; }); } }
Modify your template to bind the button state:
<h1> WorkZone TODO </h1> <form #f="ngForm" (submit)="form_submit(f)"> <input type="text" ngModel name="name" placeholder="TODO"> <br/> <br/> <button type="submit" [disabled]="isSubmitting"> {{ isSubmitting ? 'Submitting...' : 'Insert' }} </button> <button type="reset">Clear</button> </form>
2. Use RxJS Operators to Ignore Duplicate Requests
For a more reactive approach, you can leverage RxJS operators like exhaustMap to automatically ignore new submission requests until the current one completes. This is especially clean if you prefer working with observables over promises.
Update your component to use RxJS:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { NgForm } from '@angular/forms'; import { AngularFireDatabase } from '@angular/fire/compat/database'; import { Subject, from } from 'rxjs'; import { exhaustMap, takeUntil } from 'rxjs/operators'; export class InsertTODO implements OnInit, OnDestroy { // Subject to handle submission events private submit$ = new Subject<NgForm>(); // Subject to clean up subscriptions on component destroy private destroy$ = new Subject<void>(); constructor(private angularFire: AngularFireDatabase) { } ngOnInit() { this.submit$.pipe( // Ignore new requests until the current one finishes exhaustMap(f => from(this.angularFire.list('todos').push({ name: f.controls.name.value }))), // Clean up when component is destroyed takeUntil(this.destroy$) ).subscribe({ next: (t) => { console.log('recorded data ' + t.key); this.clearForm(t.form); // You can add a helper method to clear the form }, error: (e) => console.log(e.message) }); } form_submit(f: NgForm) { this.submit$.next(f); } private clearForm(f: NgForm) { f.controls.name.setValue(''); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
3. Add Backend-Level Protection with Firebase Rules
Frontend protections can be bypassed (like the JS script you showed), so adding backend validation is non-negotiable. You can use Firebase Realtime Database rules to limit submission frequency and enforce valid data.
Update your Firebase Realtime Database rules (in the Firebase Console):
{ "rules": { "todos": { ".write": "auth != null && newData.child('createdAt').val() > now - 5000", ".validate": "newData.hasChild('name') && newData.hasChild('createdAt') && newData.child('name').val().length > 0" } } }
Then modify your component to include a createdAt timestamp in each submission:
form_submit(f: NgForm) { if (this.isSubmitting) return; this.isSubmitting = true; this.angularFire.list('todos').push({ name: f.controls.name.value, createdAt: Date.now() // Add timestamp }).then((t: any) => { console.log('recorded data ' + t.key); f.controls.name.setValue(''); }, (e: any) => { console.log(e.message); }).finally(() => { this.isSubmitting = false; }); }
This rule ensures:
- Only authenticated users can submit todos
- A user can’t submit more than one todo every 5 seconds
- Each todo must have a valid
nameandcreatedAtfield
内容的提问来源于stack exchange,提问作者Eduardo Lopes

