Is Validators.required a necessity in Angular 2?

Is there a way to dynamically require a form field based on conditions? I created a custom validator, but the conditional variables passed to it remain static. How can I update these conditional values within the custom validator function? Is it possible to achieve this using Validators.required instead of a custom validator?

private foo: boolean = false;
private bar: boolean = true;

constructor(private _fb: FormBuilder) {
    function conditionalRequired(...conditions: boolean[]) {
      return (control: Control): { [s: string]: boolean } => {
        let required: boolean = true;
        for (var i = 0; i < conditions.length; i++) {
          if (conditions[i] === false) {
            required = false;
          }
        }
        if (required && !control.value) {
          return { required: true }
        }
      }
    }
    this.applyForm = _fb.group({
          'firstName': ['', Validators.compose([
            conditionalRequired(this.foo, !this.bar)
          ])],
          ...
    });
}

Update (May 17, 2016)

It has been quite some time since I posted this query. I would like to mention the .include() and .exclude() methods provided by the ControlGroup class for those looking to implement similar functionality. (docs) While a conditional Validator may have its use cases, I have found that including and excluding controls, control groups, and control arrays is an effective approach. Simply apply the required validator to the desired control and manage its inclusion/exclusion accordingly. Hopefully, this information proves helpful to someone!

Answer №1

I have customized a more versatile version by creating an additional validator that can be combined with other validators. While I am still in the initial stages of exploring the forms module, this code may not be the most efficient or cover all edge cases, but it serves as a solid foundation for typical use cases and can benefit others.

Designed for rc.4 and the new forms module, please exercise caution with the revalidateOnChanges feature (the implementation method may not be optimal). Use at your own discretion! :)

Usage Instructions

The validator requires two arguments: a conditional function that evaluates the formGroup to determine if validation should occur (true/false), and a validator (which can be a combination of multiple validators). The field will undergo revalidation when the formGroup is updated, currently limited to checks within the same formGroup, but this restriction can be easily addressed.

this.formBuilder.group({
    vehicleType: ['', Validators.required],
    licencePlate: [
        '',
        ExtraValidators.conditional(
            group => group.controls.vehicleType.value === 'car',
            Validators.compose([
                Validators.required,
                Validators.minLength(6)
            ])
        ),
    ]
});

In the provided example, there are two fields: vehicleType and licencePlate. If the vehicleType is "car," the composed validator (required and minLength) will be applied.

Compose allows for applying multiple conditionals simultaneously or independently. Here's a somewhat more intricate scenario:

this.formBuilder.group({
    country: ['', Validators.required],
    vehicleType: ['', Validators.required],
    licencePlate: [
        '',
        Validators.compose([
            ExtraValidators.conditional(
                group => group.controls.vehicleType.value === 'car',
                Validators.required
            ),
            ExtraValidators.conditional(
                group => group.controls.country.value === 'sweden',
                Validators.minLength(6)
            ),
        ])
    ]
});

In this instance, required will be enforced if the type is "car," and minLength will apply if the country is "Sweden." Each validation will trigger based on its specific condition(s).

About the Validator Itself

Note that object comparison is straightforward due to working with small objects; utilizing tools like Ramda could streamline the code significantly.

export class ExtraValidators {
    static conditional(conditional, validator) {
        return function(control) {
            revalidateOnChanges(control);

            if (control && control._parent) {
                if (conditional(control._parent)) {
                    return validator(control);
                }
            }
        };
    }
}
function revalidateOnChanges(control): void {
    if (control && control._parent && !control._revalidateOnChanges) {
        control._revalidateOnChanges = true;
        control._parent
            .valueChanges
            .distinctUntilChanged((a, b) => {
                // Simple comparison for plain objects from the form
                /* Comparison logic */
            })
            .subscribe(() => {
                control.updateValueAndValidity();
            });

        control.updateValueAndValidity();
    }
    return;
}

NOTE: Make sure to import the operator:
import 'rxjs/add/operator/distinctUntilChanged';

Answer №2

After reviewing your comment, a potential issue has been identified. The current implementation uses primitive types for conditions in the validator function, causing it to only consider the initial values provided. Even if these values are updated later on, the validator won't reflect those changes.

To address this issue, using an object to store conditions is recommended:

private foo: boolean = false;
private bar: boolean = true;

private conditions: any = {
  condition1: foo,
  condition2: !bar
};

constructor(private _fb: FormBuilder) {
    function conditionalRequired(conditions: any) {
      return (control: Control): { [s: string]: boolean } => {
        let required: boolean = true;
        for (var elt in conditions) {
          var condition = conditions[elt];
          if (conditions === false) {
            required = false;
          }
        }
        if (required && !control.value) {
          return { required: true };
        }
      }
    }
    this.applyForm = _fb.group({
          'firstName': ['', Validators.compose([
            conditionalRequired(conditions)
          ])],
          ...
    });
}

By utilizing this approach, the conditions can be easily updated by reference. To modify the conditions, follow these steps:

updateConditions() {
  this.conditions.condition1 = true;
  this.conditions.condition2 = true;
}

For demonstration purposes, you can access a live example at: https://plnkr.co/edit/bnX7p0?p=preview.

Update:

If you wish to trigger the validator when updating the conditions, make sure to explicitly call the updateValueAndValidity method of the control. This will ensure that the valid attribute of both the control and form are appropriately adjusted:

updateConditions() {
  this.conditions.condition1 = true;
  this.conditions.condition2 = true;
  this.applyForm.controls.firstName.updateValueAndValidity();
}

Answer №3

Dynamic validation for the 'required' field can be set using the setValidators() method after identifying the controls -

this.registrationForm.get('username').setValidators(applyValidation());

applyValidation() {
        if(condition) {
            return [Validators.required];
        } else {
            return [];
        }   
    }

Answer №4

I devised a validation method that utilizes a function callback to make the validator reusable, but I am struggling to automate the call to updateValueAndValidity() on the control linked to this validator without needing manual intervention every time the value of another control changes.

Custom Validator Implementation

export class ValidationService {
    static conditionalRequired = (isRequiredFunction: Function) => {
        return (control: Control) => {
           if (!control.value && isRequiredFunction())
           ...

Example Usage in Component

private myControl1: Control = 
    new Control("", ValidationService.conditionalRequired(() => 
        { return this && this.model && this.model.SomeProperty === 'SomeValue' } ));

private myControl2: Control = 
    new Control("", 
    ValidationService.conditionalRequired(this.isControl2Required.bind(this)));

isControl2Required() { 
    return someCondition;
}

Answer №5

To easily enable or disable a control, you can utilize the enable() and disable() methods on the AbstractControl.

this.myControl.valueChanges.subscribe(
      value => {
        if (value) {
          this.otherControl.enable();
        } else {
          this.otherControl.disable();
        }
      }
    );

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

How can we prevent users from changing URLs or accessing pages directly in Angular 7 without using authguard?

Hey there! I am trying to find a way to prevent users from accessing different pages by changing the URL, like in this https://i.sstatic.net/E2e3S.png scenario. Is there a method that can redirect the user back to the same page without using Authguard or a ...

In Ionic 2, trying to access the IONIC_ENV variable consistently results in it being undefined

Does anyone know how to access the IONIC_ENV variable in order to switch between using the API URL for production and development environments? Every time I try to check it, it returns undefined. process.env.IONIC_ENV I might need to run or add another c ...

Webpack resolve.alias is not properly identified by Typescript

In the Webpack configuration, I have set up the following: usersAlias: path.resolve(__dirname, '../src/pages/users'), In my tsconfig.json, you can find: "baseUrl": ".", "paths": { "usersAlias/*": ["src/pages/users/*"], } This is how the cod ...

Combining rxjs mergeMap with a response that yields an array

Currently, I am working on an ajax request that retrieves an Array of links. My goal is to utilize this array to make separate ajax requests for each link and then combine all the responses together. Here is how I have begun: ajax.post( url, data ).pipe( ...

Utilizing conditional date parameter in Angular's in-memory web API for an HTTP request

In my current project, there is a collection of objects referred to as members members = [ {id:'1', firstName:'John', lastName:'Black', birthDate:'1956-11-22', gender:'Male', email:'<a href="/c ...

What is the best way to obtain clear HTTP request data in a component?

My service retrieves JSON data from the backend: constructor(private http: Http) { }; getUsers(): Observable<any> { return this.http.get('http://127.0.0.1:8000/app_todo2/users_list'); }; In the component, this data is processed: ng ...

Can you explain the distinction between using tsserver and eslint for linting code?

As I work on setting up my Neovim's Native LSP environment, a question has arisen regarding JS/TS linter. Could someone explain the distinction between tsserver and eslint as linters? I understand that tsserver is a language server offering features ...

Is it feasible to connect to an output component without using EventEmitter?

When it comes to creating components, I've found it quite easy to use property binding for inputs with multiple options available like input(). However, when dealing with component outputs, it can be a bit complicated as there's only one option u ...

The conflict between Apple App Site Association and an angular route is causing issues

Can someone provide guidance on setting up an iOS app link to work with Angular? My goal is to include a link in emails sent to users that will open the app if it's installed. I've placed a file named 'apple-app-site-association' witho ...

The frontend is not triggering the Patch API call

I am having trouble with my http.patch request not being called to the backend. This issue only occurs when I try calling it from the frontend. Oddly enough, when I tested it in Postman, everything worked perfectly. Testing the backend on its own shows t ...

The Type X is lacking essential properties found in Type Y, including length, pop, push, concat, and an additional 26 more properties. Code: [2740]

One interesting thing I have noticed is the Product interface: export interface Product{ code: string; description: string; type: string; } There is a service with a method that calls the product endpoint: public getProducts(): Observable<Product ...

Navigating the world of NestJs and TypeScript with the `mongoose-delete` plugin: a comprehensive guide

I am currently utilizing Mongoose within the NestJs library and I want to incorporate the mongoose-delete plugin into all of my schemas. However, I am unsure of how to implement it with nestJS and Typescript. After installing both the mongoose-delete and ...

What is the reason for the inability of `Array<Value>, Value` to properly retrieve the type of the array value?

I am encountering an issue with the type declaration below: function eachr<Subject extends Array<Value>, Value>( subject: Subject, callback: ( this: Subject, value: Value, key: number, subject: Subject ...

Discover how to retrieve service response data from an API and populate it into the Select Option with Angular 2

Api.services.ts getListOfNames() { return this.http.get(this.BaseURL + 'welcome/getnama') .pipe(map(response => { return response; })); } After making the API call, I receive the following resp ...

The plugin "proposal-numeric-separator" was not found. Please make sure that there is a corresponding entry for it in the ./available-plugins.js file

{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "myProjects", "projects": { "uniqueApp": { "projectType": "web-app", "schematics": {}, "root": "", "sourceRoot": "src", ...

Posting forms in NextJS can be optimized by utilizing onChange and keypress events for input fields

I am currently working on my first Edit/Update form within a newly created NextJs application as I am in the process of learning the framework. I seem to be facing an issue where the form constantly posts back to the server and causes the page to refresh ...

Directing non-www to www in Next.js has never been easier

Based on the information I've gathered, it seems that using www is more effective than not using it. I am interested in redirecting all non-www requests to www. I am considering adding this functionality either in the next.config.js file or, if that& ...

The type 'Data' is lacking the following attributes from its definition

Being a newcomer to Angular 8, I can't figure out why this error is popping up. If you have any suggestions on how to improve the code below, please feel free to share your tips. The error message reads: Type 'Data' is missing the follo ...

What is the best way to handle waiting for an HTTP request to complete from a separate component?

https://i.sstatic.net/q4XYB.png An issue arises when calling the GetData function from a component's controller too early. I would like it to wait for the identification process to complete before triggering. During page loading, there is a server c ...

The SWT Browser is failing to display Angular 2 pages within the Eclipse view

I attempted to embed Angular 2 HTML pages within the SWT Browser widget, but it seems that the Angular 2 HTML pages are not displaying correctly inside the SWT Browser. However, I had no trouble embedding Angular 1 (or Angular JS) pages within the SWT bro ...