You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 21:38:09