Question

Why is my boolean status not resetting after Subscribe method?

I have a simple Submit button and have Mat-Spinner to show the progress. After the call either its Success or Error, my isLoading is not setting to False, because of which my spinner is showing always. I think i am missing something in Angular 18, the Standalone component. Please help

Here is the below code

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
  FormBuilder,
  FormGroup,
  FormGroupDirective,
  FormsModule,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';

import { Router } from '@angular/router';
import { MaterialModule } from '../../material-module';
import { ApiService } from 'src/app/services/api.service';
import { HttpErrorResponse } from '@angular/common/http';

@Component({
  providers: [FormGroupDirective],
  selector: 'app-basic',
  standalone: true,
  imports: [
    CommonModule,
    MaterialModule,
    FormsModule,
    ReactiveFormsModule,
  ],
  templateUrl: './basic.component.html',
  styleUrl: './basic.component.css',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BasicComponent {
  basicInfo: FormGroup;
  isLoading: boolean;
  errorMsg: string;

  constructor(
    private fb: FormBuilder,
    public router: Router,
    private apiSvc: ApiService,
  ) {}

Submit Button Code

submit() {

    if (this.basicInfo.valid) {
      this.isLoading = true;

      this.apiSvc.create(this.basicInfo.value).subscribe({
        next: (data) => {
          this.isLoading = false;
        },
        error: (error: HttpErrorResponse) => {
          this.isLoading = false;
        },
      });
    }
  }


<button
  mat-flat-button
  [disabled]="!basicInfo.valid"
  class="custom-width-button"
>
<mat-spinner
  [diameter]="24"
  class="white-spinner"
  *ngIf="isLoading"
></mat-spinner>
</button>
 2  73  2
1 Jan 1970

Solution

 0

If your component is using OnPush, you might need to manually trigger change detection.

For example:

@Component({
  selector: 'app-component',
  template: ``,
  changeDetection: ChangeDetectionStrategy.OnPush,
})

This can be manually called by referencing changeDetectorRef from your components constructor.

constructor(private ref: ChangeDetectorRef) { }

And then later used in your code..

this.ref.detectChanges();

Checks this view and its children. Use in combination with ChangeDetectorRef#detach to implement local change detection checks.

Or

this.ref.markForCheck();

When a view uses the ChangeDetectionStrategy#OnPush (checkOnce) change detection strategy, explicitly marks the view as changed so that it can be checked again.

Give this a try in your code,

 this.apiSvc.create(this.basicInfo.value).subscribe({
    next: (data) => {
      this.isLoading = false;
      this.ref.markForCheck();
    },
    error: (error: HttpErrorResponse) => {
      this.isLoading = false;
      this.ref.markForCheck();
    }
  });

https://v17.angular.io/api/core/ChangeDetectionStrategy
https://v17.angular.io/api/core/ChangeDetectorRef

2024-07-10
khollenbeck

Solution

 0

So what happened here was that since the component's change detection strategy is set to push, even though the isLoading's value is updated in the TS file, it is not detected since although it is fire an event, isLoading's value is updated inside a successful completion of a Promise. When comes to the onPush change detection strategy that change is not detect by the change detection, since angular does not know at which time that this would be completed. And here that is happened inside a manual subscription. To understand this you can have a look on this documentation.

So based on the scenario that you need to have, if you need to keep the onPush further, you can convert this isloading into an observable, and emit the new value to it inside the request completion and get that value using an async pipe in the template file.

2024-07-14
Kasuni Kuruppuarachchi