Angular - a simple method to determine the number of non-empty inputs in a complex template-driven form

As I work on multiple substantial Angular 11 template forms containing basic inputs like text, radiolists, and checkboxes, I am looking for the most effective method to calculate the percentage of completed inputs while the user is actively engaging with the form. This would involve determining the number of non-empty inputs versus the total number of inputs in the form. I currently have ngModel binding on all fields but am unsure how to create a custom validation process for this specific task.

Answer №1

Handling Input Counts with Directive and Service for State Management

  • A directive is created to detect changes in the element and trigger updates.
  • We use a service to store the state information of our form.

Important points: Please take note of the following details:

  • The Service is currently set at the root level, limiting it to work for only one form. To handle multiple forms, you will need to provide the service at a component level or extend the state logic to support multiple forms using keys or similar methods.
  • You may consider replacing the service with more advanced state management systems. The decision to use a service was based on its simplicity.

Note: This solution recalculates the total percentage every time any input is changed for simplicity. For larger forms, optimizing by checking if an input has been counted already may be more efficient.

The Directive

This directive assumes that it will only be used with HTMLInputElement types.

@Directive({selector: '[countInput]'})
export class CountInputDirective implements AfterViewInit, OnDestroy {

  private subscription = new Subscription();

  constructor(private host: ElementRef<HTMLInputElement>, private countService: CountingService) {
  }

  ngAfterViewInit() {
    this.countService.addInput(this.host.nativeElement);
    this.subscription.add(
      fromEvent(this.host.nativeElement, 'keydown')
        .pipe(debounceTime(350))
        .subscribe(change => {
          this.countService.inputStateChanged()
        })
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
    this.countService.removeInput(this.host.nativeElement);
  }
}

You can adjust the event binding according to your specific requirements if not all inputs support key events.

The Service

@Injectable({providedIn: 'root'})
export class CountingService {
  private _inputs: HTMLInputElement[] = [];
  private formUpdateEvent: EventEmitter<void> = new EventEmitter();
  private _filled: number = 0;

  get total() {
    return this._inputs.length;
  }

  get percentageDone() {
    return Math.round(this._filled / Math.max(this.total, 1) * 100);
  }

  get formUpdated() {
    return this.formUpdateEvent.asObservable();
  }

  addInput(element: HTMLInputElement) {
    this._inputs.push(element);
  }

  removeInput(element: HTMLInputElement) {
    const index = this._inputs.indexOf(element);
    if (index > -1) {
      this._inputs.splice(index, 1);
    }
  }

  inputStateChanged() {
    this.recalculateState();
    this.formUpdateEvent.emit();
  }

  private recalculateState() {
    this._filled = 0;
    this._inputs.forEach(element => {
      if (Boolean(element.value))
        this._filled++;
    });
  }
  
}

Implementing the Solution

If you need to perform actions when a change occurs, subscribe to the provided EventEmitter. If you simply want to display the total percentage, access the getter directly.

@Component()
 // inject the service in the component with your form.
 constructor(private count: CountingService) {}
<!-- apply the directive to all inputs requiring counting -->
<form action="">
  <input type="text" countInput>
  <input type="text" countInput>
  <input type="text" countInput>
</form>
{{count.percentageDone}}
</form>

Answer №2

When utilizing template-driven forms, adding custom validation directly to the form is not straightforward. However, there is a workaround to achieve this. By implementing an input change event for the specific control requiring custom validation logic, we can handle it separately. To monitor the progress of a large form, one approach is to break it down into smaller form groups and manage events like on input change specifically for each group. This strategy helps in avoiding the need to iterate through a vast collection of form controls by applying the input change event to the form itself.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

preserving the current state even after refreshing the page

Currently, I am teaching myself React. When I click on the favorites button, which is represented by a heart symbol, it changes color. However, upon refreshing the page, the color change disappears. To address this issue, I came across the following helpfu ...

Vanishing ShareThis Link After Postback in JavaScript

On my webpage at , I have included a ShareThis link in the footer that is generated via Javascript. However, whenever there is an AJAX postback after entering an email on the site, the link disappears. Is there a way to prevent this from happening and ensu ...

Creating dynamic input fields in JavaScript by using class-based methods

I have a requirement to dynamically duplicate a couple of fields whenever the user clicks on the Add button and then capture the data from these fields. While searching online, I came across various tutorials like Tutorial which demonstrate how to create n ...

How can I retrieve elements i from each array in HandlebarsJS and then access element j, and so on?

Given a JSON like this: { "network": [ { "name": [ "John", "Jill", "June" ] }, { "town": [ "London", "Paris", "Tokyo" ] }, { "age" : [ "35", "24", "14" ] } ] } Unfortunately, my data is in this format and I have to work w ...

What is the process for updating parameters before finalizing a route in Vue.js?

I have set up a route with a value parameter: routes.push({ name: 'test', path: '/test/:value', component: resolve(__dirname, 'src/pages/test.vue'), }); Now, I want to modify the route so that it also include ...

Invoke ajax to reset session once user navigates away from page

I am looking for a solution to automatically clear the PHP session array every time a user exits my webpage. However, I need to exclude cases where the user clicks on links with query strings. I attempted using Javascript code, but it did not work as expec ...

Search through the directory of images and generate a JSON representation

I'm looking for a way to transform a URL-based directory of images into a Json object, which I can then utilize and connect within my Ionic project. Despite extensive searching, I have yet to find any satisfactory solutions to this challenge. Thus, I ...

How do I access the current state in Ngrx Store without the need to subscribe, specifically for use in a route Resolve?

Presently, my Resolve implementation is quite straightforward: export class UserResolve implements Resolve<any>{ constructor(private userService: UserService){} resolve(route: ActivatedRouteSnapshot){ return this.userService.get(route. ...

Please enter only numerical values using jQuery

Currently, I am facing a slight issue. My goal is to only run the code when the input characters are numbers. This is the snippet of code I have been working with: $(".jq-sales, .jq-variablecosts, .jq-fixedcosts, .jq-additional-sales, .jq-sales-units, .j ...

Checking variable length time with JavaScript Regular Expression

My specific requirement is to validate a string ranging from 1 to 4 characters in length (where H stands for hour and M represents minutes): If the string has 1 character, it should be in the format of H (a digit between 0-9). A 2-character string should ...

Update the CSS dynamically using JavaScript or AngularJS

Is there a way to dynamically modify this CSS style using JavaScript or in an Angular framework? .ui-grid-row.ui-grid-row-selected > [ui-grid-row] > .ui-grid-cell{ background-color: transparent; color: #0a0; } .ui-grid-cell-focus ...

Experiencing difficulties establishing a connection between the React client and Node.js server using SocketIO

I am currently getting familiar with socketIO and have successfully set up a basic nodejs server. However, I am facing an issue where my nextJS app is unable to connect to the server as expected. Despite no errors being displayed, the messages that should ...

Preventing the detection of a jshint "error"

When creating an object using the constructor X, I later add multiple methods in the file using X.prototype.method = function () {...}. Although technically an assignment statement, it behaves like a function declaration, which typically doesn't requi ...

I'm having trouble with my Typescript file in Vscode - every time I try to edit the css code, all the text turns red. Can someone

Check out this visual representation: [1]: https://i.stack.imgur.com/9yXUJ.png Followed by the corresponding code snippet. export const GlobalStyle = createGlobalStyle` html { height: 100%; } body { background-image: url(${BGImage}); ba ...

Correctly inputting the 'in' statement - Avoid using a primitive value on the right side of an 'in' statement

I am currently facing an issue with my React code where I am getting the error message: The right-hand side of an 'in' expression must not be a primitive.. I am unsure about how to resolve this problem effectively: // The goal is to allow nu ...

Save canvas as an image, with the option to incorporate additional text from a textarea element on the page into

I am currently working with a canvas element using Fabric.JS. Beneath the canvas on the screen, there is a textarea within the DOM. The download button on the page successfully downloads the canvas element as an image. However, I now need to include the t ...

JavaScript unable to access elements during page loading

I am facing an issue with the following code: var coll = document.getElementsByClassName("collapsible"); var i; for (i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function() { this.classList.toggle("active"); v ...

Issue with Material UI dropdown not selecting value on Enter key press

I have encountered an issue with the material UI dropdown component that I am using. When I navigate through the dropdown options using arrow keys and press enter, the selected value does not get highlighted. Below is the code snippet for the dropdown: ...

Are NPM and Eslint supposed to be this confusing, or am I missing something?

Recently, I've started delving into the world of JS and have been eager to learn more about linting. Following a tutorial, we set up the lint stage in our package.json file. The configuration looks like this: "lint": "./node_modules/.bin/eslint ." U ...

A class definition showcasing an abstract class with a restricted constructor access

Within my codebase, there is a simple function that checks if an object is an instance of a specific class. The function takes both the object and the class as arguments. To better illustrate the issue, here is a simplified example without delving into th ...