Angular创建交易后跳转列表页无数据,刷新后正常的原因排查
问题描述
我有两个Angular子页面:/create-transaction 用于创建交易数据,/transactions 用于以表格展示交易数据。创建交易并成功写入PostgreSQL数据库后,跳转至/transactions页面无法看到新生成的交易数据;但按下F5刷新页面或再次点击导航栏的对应链接后,数据可正常显示。想了解跳转页面后无法立即显示数据的原因。
相关代码
transaction-list.ts
import { Component, inject, OnInit, signal } from '@angular/core'; import { NgClass } from '@angular/common'; import { TransactionsService } from '../../../core/services/transactions'; import { Transaction } from '../../../core/models/transaction.model'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-transaction-list', standalone: true, imports: [NgClass, CommonModule], templateUrl: './transaction-list.html', styleUrl: './transaction-list.scss', }) export class TransactionList implements OnInit { loading = signal(false); private readonly transactionsService = inject(TransactionsService); transactions: Transaction[] = []; ngOnInit(): void { this.loadTransactions(); } loadTransactions(): void { this.loading.set(true); this.transactions = []; this.transactionsService.getTransactions().subscribe({ next: (data) => { console.log('Transactions loaded:', data); this.transactions = [...data]; this.loading.set(false); }, error: (error) => { console.error('Error loading transactions:', error); this.loading.set(false); } }); } }
create-transaction.ts
import { Component, EventEmitter, inject, Output, signal } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { CategoriesService } from '../../../core/services/categories'; import { Category } from '../../../core/models/category.model'; import { TransactionsService } from '../../../core/services/transactions'; import { Transaction } from '../../../core/models/transaction.model'; @Component({ selector: 'app-create-transaction-form', standalone: true, imports: [CommonModule, ReactiveFormsModule], templateUrl: './create-transaction-form.html', styleUrl: './create-transaction-form.scss', }) export class CreateTransactionForm { private readonly fb = inject(FormBuilder); private readonly transactionsService = inject(TransactionsService); @Output() transactionCreated = new EventEmitter<Transaction>(); private readonly categoriesService = inject(CategoriesService); categories: Category[] = []; loading = signal(false); form = this.fb.nonNullable.group({ transactionName: ['', [Validators.required, Validators.minLength(2)]], transactionType: ['income'], transactionAmount: ['', [Validators.required, Validators.min(0.01)]], transactionCategory: ['', [Validators.required]], transactionDate: ['', [Validators.required]], transactionDescription: ['',/* [Validators.required, Validators.maxLength(200)]*/], }); onSubmit(): void { if (this.form.invalid) { this.form.markAllAsTouched(); return; } this.loading.set(true); const formValue = this.form.getRawValue(); const payload = { name: formValue.transactionName, type: formValue.transactionType, amount: Number(formValue.transactionAmount), category: formValue.transactionCategory, completedAt: formValue.transactionDate, description: formValue.transactionDescription }; this.transactionsService.createTransaction(payload).subscribe({ next: (transaction) => { this.transactionCreated.emit(transaction); this.form.reset({ transactionName: '', transactionType: 'income', transactionAmount: '', transactionCategory: '', transactionDate: '', transactionDescription: '', }); this.loading.set(false); }, error: (error) => { console.error('Error creating transaction: ', error); this.loading.set(false); } }); } ngOnInit(): void { this.loadCategories(); } loadCategories(): void { this.loading.set(true); this.categoriesService.getCategories().subscribe({ next: (data) => { this.categories = data; this.loading.set(false); }, error: (error) => { console.error('Error loading categories:', error); this.loading.set(false); }, }); } }
TransactionService (transaction.ts)
import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Transaction } from '../models/transaction.model'; @Injectable({ providedIn: 'root', }) export class TransactionsService { private readonly apiUrl = 'http://localhost:3000/transactions'; private readonly http = inject(HttpClient); getTransactions(): Observable<Transaction[]> { return this.http.get<Transaction[]>(this.apiUrl); } createTransaction( data: { name: string, type: string, amount: number, category: string, completedAt: string, description: string, } ): Observable<Transaction> { return this.http.post<Transaction>(this.apiUrl, data); } }
transaction-list.html
<table class="transactions-table"> <thead> <tr> <th>Názov</th> <th>Typ</th> <th>Suma</th> <th>Kategória</th> <th>Zrealizovaná dňa</th> <th>Popis</th> </tr> </thead> <tbody> @for (t of transactions; track t.amount) { <tr> <td>{{ t.name }}</td> <td> <span [ngClass]="t.type"> {{ t.type === 'income' ? 'Príjem' : 'Výdavok' }} </span> </td> <td>{{ t.amount }} €</td> <td>{{ t.category }}</td> <td>{{ t.completedAt | date: 'dd. MM. yyyy' }}</td> <td>{{ t.description }}</td> </tr> } </tbody> </table>
原因分析
核心问题是Angular的路由复用机制:
- 当从
/create-transaction跳转回/transactions时,Angular默认会复用已初始化的TransactionList组件实例,不会重新执行ngOnInit钩子。 - 你的交易数据仅在
ngOnInit中调用loadTransactions加载一次,因此新创建的交易不会被重新获取。 - 刷新页面会重新初始化整个应用,重新点击导航链接会强制Angular销毁旧组件实例并创建新实例,此时
ngOnInit会再次执行,从而加载最新数据。
解决方案
方案1:监听路由导航事件,重新加载数据
在TransactionList组件中添加路由监听,每次进入/transactions页面时重新获取数据:
import { Router, NavigationEnd } from '@angular/router'; import { takeUntil, Subject } from 'rxjs'; export class TransactionList implements OnInit, OnDestroy { private readonly router = inject(Router); private destroy$ = new Subject<void>(); ngOnInit(): void { this.loadTransactions(); // 监听路由变化,进入当前页面时重新加载 this.router.events.pipe(takeUntil(this.destroy$)).subscribe(event => { if (event instanceof NavigationEnd && event.url.includes('/transactions')) { this.loadTransactions(); } }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } // ... 其他代码不变 }
方案2:通过服务共享更新事件
修改TransactionsService,用Subject通知列表组件更新数据:
import { tap } from 'rxjs'; import { Subject } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class TransactionsService { private transactionsUpdated = new Subject<void>(); // 提供订阅接口 onTransactionsUpdated() { return this.transactionsUpdated.asObservable(); } createTransaction(data: /* 类型定义 */): Observable<Transaction> { return this.http.post<Transaction>(this.apiUrl, data).pipe( tap(() => { // 创建成功后触发更新事件 this.transactionsUpdated.next(); }) ); } // ... 其他代码不变 }
然后在TransactionList中订阅更新事件:
export class TransactionList implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); ngOnInit(): void { this.loadTransactions(); // 订阅更新事件,自动刷新数据 this.transactionsService.onTransactionsUpdated() .pipe(takeUntil(this.destroy$)) .subscribe(() => this.loadTransactions()); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } // ... 其他代码不变 }
方案3:修改路由复用策略
在路由配置中设置强制刷新:
const routes: Routes = [ { path: 'transactions', component: TransactionList, runGuardsAndResolvers: 'always' // 每次导航都执行守卫和解析器 }, // ... 其他路由 ]; @NgModule({ imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' // 同一URL导航时重新加载 })], exports: [RouterModule] }) export class AppRoutingModule { }
内容的提问来源于stack exchange,提问作者nonamecoder
相关产品推荐
相关产品推荐

