• 25-Jan-2023
Lightrun Team
Author Lightrun Team
Share
This article is about fixing FormGroup.disable() and FormGroup.enable() do not allow resetting disabled state in Angular Angular

FormGroup.disable() and FormGroup.enable() do not allow resetting disabled state in Angular Angular

Lightrun Team
Lightrun Team
25-Jan-2023

Explanation of the problem

I am submitting a bug report related to the behavior of ngForm when dealing with the disabling of included controls.

When using the following code to disable all included controls within an ngForm:

<form (ngSubmit)="onSubmit()" #form="ngForm">
   <input [disabled]="disabled">
   <input disabled="true"> <!-- always disabled -->
</form>

I am able to access the form from within the component controller and overwrite the formGroup states using the following code:

this.form.form.disable();
this.form.form.enable();

However, when doing this, all controls are disabled and then all are enabled, even the second form which has the disabled attribute hard-coded. There is currently no way to reset the disabled state of this form.

The expected behavior is to have one of the following solutions:

  • The most specific attribute ([disabled] on the input) takes precedence.
  • A “force” option in disable() and enable()
  • A method on AbstractControl to reset the disabled state.

The motivation for changing this behavior is to be able to build forms that enforce a state during a certain process (e.g. async update) and then reset the disabled states after the async operation is completed.

Environment:

  • Angular version: 4.4.1
  • Browser:
    • Chrome (desktop) version XX
    • Chrome (Android) version XX
    • Chrome (iOS) version XX
    • Firefox version XX
    • Safari (desktop) version XX
    • Safari (iOS) version XX
    • IE version XX
    • Edge version XX

A minimal reproduction of the problem with instructions can be found at http://plnkr.co/edit/M71eHXdG8DcNJOYZDuhe. The plnkr has two inputs: one “regular” and one is always disabled. When clicking the submit button, both inputs get disabled. After a timeout, both inputs get enabled. The “always disabled” input cannot be reset.

Troubleshooting with the Lightrun Developer Observability Platform

Getting a sense of what’s actually happening inside a live application is a frustrating experience, one that relies mostly on querying and observing whatever logs were written during development.
Lightrun is a Developer Observability Platform, allowing developers to add telemetry to live applications in real-time, on-demand, and right from the IDE.

  • Instantly add logs to, set metrics in, and take snapshots of live applications
  • Insights delivered straight to your IDE or CLI
  • Works where you do: dev, QA, staging, CI/CD, and production

Start for free today

Problem solution for FormGroup.disable() and FormGroup.enable() do not allow resetting disabled state in Angular Angular

It is not possible to reset the disabled state of the controls after they have been enabled. The problem arises because when the form is enabled, all controls are enabled, even the second form which has the disabled attribute hard-coded. This can be a problem when trying to enforce a certain state during a process and then reset the disabled states after the process is completed.

The proposed solution is to use a workaround that allows for the conditional enabling or disabling of forms using the “formsDisabledVariable” in the HTML template. The solution is to use the attribute binding [attr.disabled] and set it to the “formsDisabledVariable” in the HTML file.

<formly-form [attr.disabled]="formsDisabledVariable">

In the TypeScript file, the variable “formsDisabledVariable” is set to a boolean value of false.

formsDisabledVariable: boolean = false;

To disable the form, the value of “formsDisabledVariable” is set to true, and to re-enable the form, the value is set to null.

this.formsDisabledVariable = true;
this.formsDisabledVariable = null;

It is important to note that setting the value to null instead of false is required to re-enable the form. This is because setting it to false will not work, as the attribute binding [attr.disabled] only accepts a truthy or falsy value, not a boolean. In this way, the forms can be conditionally enabled or disabled as per the requirement.

In summary, the solution is to use attribute binding and a variable in the typescript file to achieve the desired behavior of enabling or disabling the form. The formsDisabledVariable is used in the HTML template and set to true or null in the typescript file. This allows for the conditional enabling or disabling of forms and also allows for resetting the disabled state of controls.

Other popular problems with Angular

Problem: Change Detection Performance

One of the most common problems with Angular is related to change detection performance. Angular uses a mechanism called change detection to update the view when the component’s data changes. However, if the component has a lot of bindings or the change detection runs too often, the performance can suffer. This can lead to slow rendering times and a poor user experience.

Solution:

One solution to this problem is to use the OnPush strategy for change detection. This strategy only runs change detection when an input property of the component changes, instead of running it for all bindings. To use the OnPush strategy, you need to set the changeDetection property of the component to ChangeDetectionStrategy.OnPush.

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

Another solution is to use the async pipe for bindings that are observables. This pipe automatically subscribes to the observable and unsubscribes when the component is destroyed, and also triggers change detection only when the observable emits a new value.

@Component({
  selector: 'app-my-component',
  template: `
    <div *ngFor="let item of items | async"></div>
  `
})
export class MyComponent {
  items: Observable<Item[]> = this.service.getItems();
}

Problem: Memory Leaks

Another common problem with Angular is related to memory leaks. Memory leaks occur when an object is no longer in use but is still being held in memory by other objects. This can happen when a component is not properly unsubscribed from observables or event emitters, or when a component is not properly cleaned up during the destruction process.

Solution:

To avoid memory leaks, it is important to unsubscribe from observables and event emitters when a component is destroyed. One way to do this is to use the ngOnDestroy lifecycle hook. In this hook, you can call the unsubscribe method of the subscription or use takeUntil operator.

export class MyComponent implements OnInit, OnDestroy {
  private unsubscribe$ = new Subject<void>();

  ngOnInit() {
    this.service.getItems()
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(items => this.items = items);
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}

Another way to avoid memory leaks is to use the async pipe for observables and event emitters. This pipe automatically unsubscribes when the component is destroyed.

<div *ngFor="let item of items | async"></div>

Problem: Routing and Navigation

Routing and navigation is one of the most important parts of any single-page application, and it’s also one of the most common sources of problems in Angular. Issues can range from routing configuration to navigation events and guards.

Solution:

One solution to this problem is to use the Angular Router module, which provides a powerful and flexible way to handle routing and navigation. This module provides a powerful way to configure routes and navigate between different parts of your application.

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Another solution is to use guards. Guards are classes that can be used to protect access to certain routes. They can be used to prevent a user from navigating to a route or to prompt the user for confirmation before navigating.

@Injectable()
export class CanActivateGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    // code to check if the user is allowed to activate the route
    return true;
  }
}

In the routing configuration, the guard can be added to a route to protect it.

const routes: Routes = [
  { path: 'secret', component: SecretComponent, canActivate: [CanActivateGuard] }
];

This way the routing and navigation in the application can be handled properly and also it’s possible to restrict access to certain routes based on certain conditions.

A brief introduction to Angular

Angular is a JavaScript framework for building web applications. It is built on top of the popular JavaScript framework, AngularJS. Angular is a complete rewrite of AngularJS and is designed to be more efficient, modular, and easier to use. It is also built with TypeScript, a superset of JavaScript that provides static typing and other features that make it easier to write and maintain large applications.

Angular uses a component-based architecture, where an application is composed of a tree of components. Each component is responsible for a specific part of the application’s UI and logic. Components can also be nested, allowing for a hierarchical and modular structure. Angular also provides a powerful template language that allows developers to declaratively describe the UI of a component. The template language is tightly integrated with the component’s logic, making it easy to create complex and interactive UIs. Angular also provides a powerful set of directives and pipes that can be used to manipulate the DOM and transform data. The framework also provides a powerful set of services that can be used to share data and logic across the application.

Most popular use cases for Angular

  1. Building Single-Page Applications (SPAs): Angular can be used to build Single-Page Applications (SPAs). SPAs are web applications that load a single HTML page and dynamically update the content as the user interacts with the app. Angular provides a powerful set of tools for building SPAs, including a powerful router for handling client-side routing and navigation, and a powerful template language for describing the UI of the application.
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
  1. Building Progressive Web Applications (PWAs): Angular can be used to build Progressive Web Applications (PWAs). PWAs are web applications that can be installed on the user’s device and run offline. Angular provides a powerful set of tools for building PWAs, including a powerful service worker module that can be used to cache assets and provide offline support.
  2. Building Mobile Applications: Angular can be used to build mobile applications using technologies like Apache Cordova or NativeScript. This allows developers to create mobile apps using web technologies like HTML, CSS, and JavaScript, and then package them for various platforms such as iOS and Android. Angular provides a powerful set of tools for building mobile apps, including a powerful layout system that can be used to adapt the app’s UI to different screen sizes and resolutions.
Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications. It’s a registration form away.

Get Lightrun

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By submitting this form, I agree to Lightrun’s Privacy Policy and Terms of Use.