Utilizing ngControl in Angular 2 for handling optional form fields

Struggling with incorporating *ngIf and ngFormModel in form validation?

Here's the scenario: based on user input, you need to hide or disable certain fields in the form. However, if these inputs are visible, they must be validated.

When simple validation suffices, there are workarounds:

  • To enforce required input, use ngControl and required
  • For specific format requirements, the pattern attribute can be utilized outside Angular for functionality.

For more intricate validations, attempting to combine ngControl with ngFormModel for custom checks is the goal. Drawing inspiration from code snippets found on:

How to add form validation pattern in angular2 (and associated links)

Angular2 Forms :Validations, ngControl, ngModel etc

The provided code snippet looks like this:

HTML

<div>
  <h1>Issue Overview</h1>
  <form (ngSubmit)="submitForm()" #formState="ngForm" [ngFormModel]="myForm">
    <div class="checkbox">
      <label>
        <input type="checkbox" [(ngModel)]="model.hideField" ngControl="hideField"> Is the input below useless for you ?
      </label>
    </div>

    <div *ngIf="!model.hideField">
      <div class="form-group">
        <label for="optionalField">Potentially irrelevant field </label>
        <input type="text" class="form-control" [(ngModel)]="model.optionalField" ngControl="optionalField" required #optionalField="ngForm">
        <div [hidden]="optionalField.valid || optionalField.pristine" class="alert alert-warning">
          This input must go through myCustomValidator(), so behave.
        </div>
      </div>
    </div>

    <button type="submit" class="btn btn-primary" [disabled]="!formState.form.valid">I can't be enabled without accessing the input :(</button>
    <button type="submit" class="btn btn-default">Submit without using form.valid (boo !)</button>
  </form>
</div>

Typescript

import {Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core';
import {NgForm, FormBuilder, Validators, ControlGroup, FORM_DIRECTIVES}    from 'angular2/common';

@Component({
  selector: 'accueil',
  templateUrl: 'app/accueil.component.bak.html',
  directives:[FORM_DIRECTIVES],
  providers: [FormBuilder]
})
export class AccueilComponent implements AfterViewInit {

  private myForm: ControlGroup;
  model: any;

  constructor(fb: FormBuilder, cdr: ChangeDetectorRef) {
    this.cdr = cdr ;

    this.model = {} ;
    this.myForm = fb.group({
      "hideField": [false],
      "optionalField": [this.model.optionalField, Validators.compose([this.myCustomValidator])]
    });
  }

  ngAfterViewInit() {
    // Necessary to prevent "Expression has changed after it was checked" error
    this.cdr.detectChanges();
  }

  submitForm(){
    alert("Submitted !");
  }

  myCustomValidator(optionalField){
    // Replace "true" by "_someService.someCheckRequiringLogicOrData()"
    if(true) {
      return null;
    }

    return { "ohNoes": true };
  }
}

Even when an input is hidden with *ngIf, referencing the control in the constructor causes issues. This leads to difficulty in utilizing [disabled]="!formState.form.valid", as myForm remains INVALID due to the setup.

Is achieving the desired functionality possible within Angular 2? While this may be a common scenario, figuring out the solution can be tricky with existing knowledge.

Thank you!

Answer №1

One possible solution is to reset the validators assigned to your control. This entails redefining the validator function whenever a new set of validators needs to be applied to a control due to a change in state.

In this scenario, when your checkbox is checked or unchecked, you should perform the following actions:

  1. Adjust the input to be optional (non-required), while still validating it with your custom validator.
  2. Restore the control's validators to their original setup once the checkbox is unchecked.
  3. Re-validate the control to ensure that form.valid accurately reflects its status.

Check out my sample on Plnkr modeled after Angular.io's Forms Guide

if (optional) {
   this.heroFormModel.controls['name'].validator = Validators.minLength(3);
} else {
   this.heroFormModel.controls['name'].validator =
        Validators.compose([Validators.minLength(3), Validators.required]);
}

this.heroFormModel.controls['name'].updateValueAndValidity();

Answer №2

Encountering the same issue, I managed to find a solution by manually incorporating and disassociating the controls:

import {Directive, Host, SkipSelf, OnDestroy, Input, OnInit} from 'angular2/core';
import {ControlContainer} from 'angular2/common';

@Directive({
  selector: '[ngControl]'
})
export class MyFormControl implements OnInit, OnDestroy {
  @Input() ngControl:string;
  constructor(@Host() @SkipSelf() private _parent:ControlContainer) {}

  ngOnInit():void {
    // Referencing https://github.com/angular/angular/issues/6005
    setTimeout(() => this.formController.form.include(this.ngControl));
  }

  ngOnDestroy():void {
    this.formController.form.exclude(this.ngControl);
  }

  get formController():any {
    return this._parent.formController;
  }
}

To implement this successfully, all dynamic controls need to be excluded from the form initially; check out the plunkr for more information.

Answer №3

Here is an enhanced and customized version of the RC4 algorithm, now referred to as npControl for my specific requirements.

import { Directive, Host, OnDestroy, Input, OnInit } from '@angular/core';
import { ControlContainer } from '@angular/forms';

@Directive({
  selector: '[npControl]'
})
export class NPControlDirective implements OnInit, OnDestroy {
  @Input() npControl: string;

  constructor(@Host() private _parent: ControlContainer
  ) { }

  ngOnInit(): void {
    console.log('Including ', this.npControl);
    setTimeout(() => this.formDirective.form.include(this.npControl));
  }

  ngOnDestroy(): void {
    console.log('Excluding ', this.npControl);
    this.formDirective.form.exclude(this.npControl);
  }

  get formDirective(): any {
    return this._parent;
  }
}

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

Exploring the world of AWS Amplify Cognito in Angular through thorough unit

Just started diving into unit testing and decided to tackle adding test coverage for my current app. Struggling with writing the unit test cases for a specific service within an AWS file. import { Injectable } from '@angular/core'; import { Behav ...

Why is the type of parameter 1 not an 'HTMLFormElement', causing the failure to construct 'FormData'?

When I try to execute the code, I encounter a JavaScript error. My objective is to store the data from the form. Error Message TypeError: Failed to create 'FormData': argument 1 is not an instance of 'HTMLFormElement'. The issue arise ...

Encountering an issue while trying to compile my Angular 6 project - an ERROR keeps

Upon initiating my project with ng serve, I encountered a troublesome error. Despite updating my TypeScript, the issue persisted. Here is the detailed error message: ERROR in node_modules/@types/node/assert.d.ts(3,68): error TS1144: '{' or ...

How to toggle all checkboxes in Angular 2

Can anyone help me figure out how to select/unselect all checkboxes in my code? I've tried looking at other examples but haven't been successful. In the code below, I have managed to select/unselect individual units by clicking on them. When I c ...

What is the best way to set up typeahead for dynamic form fields in Angular?

Is it possible to set up the typeahead feature for dynamic form fields? You can find the stackblitzlink [link]"https://stackblitz.com/edit/angular-oxkiku" This is a snippet of my typescript code: @ViewChild('languageInstance') languageIns ...

What is the best way to ensure that Interface (or type) Properties always begin with a particular character?

I attempted to tackle this assignment using template literals, but unfortunately, I wasn't successful. Here is the interface that I am working with: interface SomeInterface { '@prop1': string; '@prop2': string; '@ ...

The functionality of Angular material seems to be malfunctioning within an Angular library

After observing the potential for reusable code in my Angular projects, I decided to create a common library to streamline development. Following the steps outlined in an article (link: https://angular.io/guide/creating-libraries), I successfully created t ...

Unexpected behavior noticed with Angular Material 2 Reactive Forms: the mat-error directive does not display when validating for minLength. However, it functions correctly for email and required

Try out this Stackblitz example: https://stackblitz.com/angular/nvpdgegebrol This particular example is a modified version of the official Angular Material sample, where the validation logic has been altered to display the mat error for minLength validati ...

How to easily deactivate an input field within a MatFormField in Angular

I've come across similar questions on this topic, but none of the solutions seem to work for me as they rely on either AngularJS or JQuery. Within Angular 5, my goal is to disable the <input> within a <mat-form-field>: test.component.h ...

Using V-For with data fetched from an axios request: A step-by-step guide

How can I dynamically populate V-Cards after making an Axios request to retrieve data? The Axios request is successful, but the v-for loop does not populate with V-Cards. I've also attempted to make the request before the rendering is completed (usin ...

Disregarding axios error due to invalid certificates while developing a visual studio code extension

I'm currently developing a Visual Studio Code extension that involves making HTTPS GET requests. I'm working on ignoring invalid certificates, specifically expired certificates. Below is a simple TypeScript script that successfully achieves this ...

Mastering the art of correctly utilizing JavaScript promises in the context of Ionic and Cordova

Here is the code snippet for Login.ts: export class LoginPage { public version:string; constructor(public navCtrl: NavController, public navParams: NavParams, private appVersion: AppVersion) { this.appVersion.getVersionNumber().then((val) => { ...

How can I convert a MongoDB document into a DTO in NestJS?

I'm currently working with a data layer that interacts with a MongoDB database. My goal is to only handle MongoDB documents in this layer without exposing the implementation details to my services. My current approach involves the following code: // ...

Dispatch a websocket communication from a synchronous function and retrieve the information within the function itself

I am currently working on an Angular project and need guidance on the most effective approach to implement the following. The requirement is: To retrieve an image from the cache if available, otherwise fetch it from a web socket server. I have managed ...

Dynamic Route Matching in NextJS Middleware

Currently, I am in the process of developing a website that incorporates subdomains. Each subdomain is linked to a file-based page using middleware. Take a look at how the subdomains are being mapped to specific pages: app.com corresponds to /home app.com ...

Issue: There is an Uncaught Promise Error indicating that there is no provider for Jsonp in the code implementation (HTML / Javascript / Typescript / Angular2)

Error displayed in screenshot: https://i.sstatic.net/bQoHZ.png .ts file code snippet (SearchDisplay.component.ts): import {Component, OnInit} from 'angular2/core'; import {Router} from 'angular2/router'; import {Hero} from './he ...

Running terminal commands in typescript

Can Typescript be used to run commands similar to JavaScript using the Shelljs Library? ...

Checking for duplicates in a TypeScript array of objects

I am facing a requirement where I must check for duplicates among object items. Within the following Array of objects, I need to specifically look for duplicates in either the "empno" or "extension" properties. If any duplicates are found, an error should ...

I am unable to showcase the image at this time

Hey there, I'm having an issue with displaying an image stored inside the NextJS API folder. The alt attribute is showing up fine, but the actual image isn't displaying. When I console.log the image data, everything seems to be in place. Can anyo ...

How can I attach a click event listener to an Angular 2 ui-router <section ui-view> element?

In my Angular 2 application, I have a list called 'views' that contains paths to multiple images. Using ng-repeat, I cycle through these images and display them one by one in a ui-view element. I want to add an event listener so that when an imag ...