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

Angular 7中ngIf首次生效后后续失效问题求助

Why Your *ngIf Loading State Stops Working After the First Click in Angular 7

Let's break down what's going wrong with your loading state and fix it step by step.

Issue Analysis

Your code has a few key issues that cause the loading state to behave unexpectedly after the first submission:

  1. Duplicate Subscriptions Pile Up
    Every time you call onSubmit(), you add a new subscription to your ProductService (a BehaviorSubject). This means:

    • When the service emits new data, all existing subscriptions will fire this.loading = false—leading to race conditions.
    • New subscriptions immediately receive the latest value from the BehaviorSubject, so loading gets set to true and then instantly reset to false before the UI can update.
  2. Misaligned Subscription with Request Trigger
    Your current component code sets loading = true and subscribes to the service, but it doesn’t explicitly trigger a new request (I assume you’re calling this.Service.query() somewhere, but even then, the subscription approach is flawed). The BehaviorSubject is meant for sharing data, not tracking individual request states.

  3. Uncleaned Subscriptions Cause Memory Leaks
    If you don’t unsubscribe when the component destroys, you’ll have lingering subscriptions that can cause unexpected state changes and memory leaks.

Solutions

We’ll cover two approaches—using Angular’s AsyncPipe (recommended) for automatic subscription management, and manual subscription cleanup for simpler use cases.

Solution 1: Use AsyncPipe (Angular Best Practice)

AsyncPipe handles subscribing and unsubscribing automatically, eliminating memory leaks and subscription bloat.

Component Code Changes

import { Subject, BehaviorSubject, tap, mergeMap, finalize } from 'rxjs';
import { ProductService, ProductCatalogSearch, GridDataResult } from './path-to-your-service';
import { FormGroup } from '@angular/forms';

export class YourComponent implements OnInit, OnDestroy {
  // Trigger to fire submission events
  private submitTrigger$ = new Subject<void>();
  // Observable to track loading state (AsyncPipe will subscribe to this)
  public loading$ = new BehaviorSubject<boolean>(false);
  public catalogForm: FormGroup;

  constructor(private productService: ProductService) {}

  ngOnInit() {
    this.submitTrigger$
      .pipe(
        // Set loading to true when submission starts
        tap(() => this.loading$.next(true)),
        // Trigger the actual request
        mergeMap(() => {
          // Replace with your actual state and form values
          const requestState = {};
          const searchForm = this.catalogForm.value as ProductCatalogSearch;
          const customerGroupCode = 'your-customer-group-code';

          // Use the fetch method directly to get the request observable
          return this.productService.fetch(requestState, searchForm, customerGroupCode);
        }),
        // Reset loading when request completes (success OR failure)
        finalize(() => this.loading$.next(false))
      )
      .subscribe((response: GridDataResult) => {
        // Update the service's BehaviorSubject with new data
        this.productService.next(response);
        // Add any other post-request logic here
      });
  }

  onSubmit() {
    // Emit a trigger to start the submission flow
    this.submitTrigger$.next();
  }

  ngOnDestroy() {
    // Clean up the subject to prevent memory leaks
    this.submitTrigger$.complete();
  }
}

HTML Template Changes

Use the async pipe to bind directly to the loading$ observable:

<form class="k-form-inline" [formGroup]="catalogForm" (ngSubmit)="onSubmit()" (keyup.enter)="onSubmit()"> 
  <button 
    class="k-button k-primary" 
    style="width:100px; margin-right:15px" 
    [disabled]="loading$ | async"
  > 
    <span class="k-icon k-i-zoom" style="padding-right: 20px"></span>
    Search 
  </button> 
  <span 
    *ngIf="loading$ | async" 
    class="k-icon k-i-loading" 
    style="font-size: 16px; padding-left:10px"
  ></span> 
</form>

Solution 2: Manual Subscription Management

If you prefer to use a boolean variable for loading, make sure to clean up subscriptions and tie the loading state directly to individual requests.

Component Code Changes

import { Subscription } from 'rxjs';
import { ProductService, ProductCatalogSearch, GridDataResult } from './path-to-your-service';
import { FormGroup } from '@angular/forms';

export class YourComponent implements OnInit, OnDestroy {
  public loading: boolean = false;
  private requestSubscription?: Subscription;
  public catalogForm: FormGroup;

  constructor(private productService: ProductService) {}

  onSubmit() {
    this.loading = true;

    // Cancel any existing request before starting a new one
    this.requestSubscription?.unsubscribe();

    // Replace with your actual request parameters
    const requestState = {};
    const searchForm = this.catalogForm.value as ProductCatalogSearch;
    const customerGroupCode = 'your-customer-group-code';

    // Subscribe directly to the request observable
    this.requestSubscription = this.productService.fetch(requestState, searchForm, customerGroupCode)
      .subscribe({
        next: (response: GridDataResult) => {
          this.productService.next(response);
          this.loading = false;
        },
        error: (error) => {
          // Always reset loading on error!
          console.error('Request failed:', error);
          this.loading = false;
        }
      });
  }

  ngOnDestroy() {
    // Clean up the subscription when component is destroyed
    this.requestSubscription?.unsubscribe();
  }
}

HTML Template

Keep your original template—no changes needed here:

<form class="k-form-inline" [formGroup]="catalogForm" (ngSubmit)="onSubmit()" (keyup.enter)="onSubmit()"> 
  <button class="k-button k-primary" style="width:100px; margin-right:15px" [disabled]="loading" > 
    <span class="k-icon k-i-zoom" style="padding-right: 20px"></span>Search 
  </button> 
  <span *ngIf="loading" class="k-icon k-i-loading" style="font-size: 16px; padding-left:10px"></span> 
</form>

Additional Tips

  • Handle Errors: Always reset the loading state in error callbacks—otherwise, your loading icon will stay visible if the request fails.
  • Separate Data Sharing from Request State: Use BehaviorSubject for sharing data across components, but tie loading state directly to individual request observables.
  • Use RxJS Operators: tap, finalize, and mergeMap make your code cleaner and reduce repetitive logic.

内容的提问来源于stack exchange,提问作者Dips

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:38:24