1

I'm having a little trouble understanding how I need to create a Custom Directive in Angular. I have an input field and a user needs to enter a string of characters, but it needs to check that the string doesn't contain certain substrings. So far I've go this function:

checkInvalidStrings(value, invalidValues) {
    let isKbWalk;
    let amount = [];
    invalidValues.forEach(string => {
        if (value.indexOf(string) > -1) {
            amount.push(string);
        } 
    })
    if (amount.length < 4) {
        isKeyboardWalk = false;
    } else {
        isKbWalk = true;
    }
    return isKbWalk;
}

I can run this in ngModel change in the component file and it works perfectly, the problem I'm having is that I want to link it with the form validation, so if it returns false, I want it to be valid, if it returns true, I want it to be invalid. I believe I need to create a custom validator directive for this, but I'm having some trouble with it. I guess what I'm asking is how do I take the function above so that I can implement it as a directive to determine if it's valid or not? I'm using template driven forms because that is what the application has been built with and it's simpler at this point to stay consistent than to change to Reactive Forms.

Generally I want to be able to do something on the input like this, where kbWalk is the directive which checks the input field for the invalid strings:

<input [(ngModel)]="data.newValue" name="newValue" #newValue="ngModel" required kbWalk>

So far I have everything imported into the app.module file but the validation isn't working correctly, here's the set up for my directive:

import { Directive, forwardRef, Attribute } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';
import { Invalid } from './invalidStrings';

@Directive({
    selector: '[kbWalk][ngModel]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: KeyboardWalkValidator, multi: true }
    ]
})
export class KeyboardWalkValidator implements Validator {
    invalidStrings = Invalid.strings;

    constructor() { }

    validate(c: AbstractControl): { [key: string]: any } {
        return null;
    }
}

Thanks for your help!

SOLVED:

import { Directive, forwardRef, Attribute, Input } from '@angular/core';
import { Validator, FormControl, NG_VALIDATORS } from '@angular/forms';
import { Invalid } from '../components/common/Invalid';

@Directive({
    selector: '[isKeyboardWalk][formControlName],[isKeyboardWalk][formControl],[isKeyboardWalk][ngModel]',
    providers: [ { provide: NG_VALIDATORS, useExisting: KeyboardWalkValidator, multi: true }
    ]
})
export class KeyboardWalkValidator implements Validator {
    invalid = Invalid.array;

    validate(c: FormControl): { [key: string]: any } {
        const val = c.value;
        return this.checkInvalidStrings(val) ? {'isKeyboardWalk': true} : null;
    } 

    checkInvalidStrings(value) { 
    // code for checking invalid value
    }
}

Then in the view I just use the isKeyboardWalk Directive:

<input type="text" isKeyboardWalk/>

My problem was in understanding that the value being passed into the validate function is actually the value from the form, so I could just use that value to run inside my checkInvalidStrings() Function and it works fine. If you guys are have a choice I would suggest using Reactive Forms and implementing the solution in the comment below which is pretty straight forward as well and helped me understand a little more.

2
  • Can you create stack blitz? Commented Jul 17, 2019 at 14:52
  • could you please tell did my answer help? or you still want solution in custom-directive approach? Commented Jul 27, 2019 at 12:45

1 Answer 1

1

We can achieve above thing using custom validor(I think we don't need to create custom directive for the above). Following works for me.

customValidatorTest.ts file

import { AbstractControl } from '@angular/forms';

export function ValidateUrl(control: AbstractControl) {
  if ( !control.value.includes('.io')) {
    return { validUrl: true };
  }
  return null;
}

component.ts file

import { FormGroup, FormArray, FormBuilder,FormControl, Validators } from '@angular/forms';
import { ValidateUrl } from '../../customValidatorTest';
@Component({
  selector: 'app-educational-details',
  templateUrl: './educational-details.component.html',
  styleUrls: ['./educational-details.component.css']
})

export class EducationalDetailsComponent implements OnInit {
    educationalForm: FormGroup;

    constructor(private formBuilder: FormBuilder){}

    ngOnInit() {
         this.educationalForm= this.formBuilder.group({

            branch:['',[Validators.required,ValidateUrl]]
         })



    }

}

component.html file

 // adding only required functionality you directly write formGrop also 
 <tbody formArrayName="educationalDetails">
        <tr  *ngFor="let educationalDetail of educationalForm.controls.educationalDetails.controls; let $i=index" [formGroupName]="$i">

 <td><input  type="text" class="form-control my-1" formControlName="branch" ></td> //if branch input text box doesnt contain ".io" then it is invalid otherwise valid

Following link might help

custom validatorar in reactive forms

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Pallamolla, your approach helped me understanding this a lot, unfortunately we're locked into using Template Driven Forms so my solution was slightly different, what I ended up doing was creating a validator directive

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.