0

I have recreated the issue here

https://stackblitz.com/edit/angular-custom-validator-issue

You can fork the setup (top right) and then edit as you please

Basically I am trying to create a custom validator in my reactive form setup that takes the value of the form input and sends off an http request to the Datamuse API that counts the number of syllables within a string. Then depending on the response result I return either an error or success. I have followed the angular docs but my solution is having problems with the http call, which I beleive is something to do with the call stack order. So I need help on how I can achieve this, I think it will help me alot in understanding Angular a bit better.

app.component

import { Component, ElementRef } from '@angular/core';
import { FormControl, FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';
import { VERSION } from '@angular/material';
import { SyllableCountValidator } from './syllable-count.service';

@Component({
  selector: 'material-app',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  version = VERSION;
  public poemForm: FormGroup;

  constructor(private fb: FormBuilder, private scv: SyllableCountValidator) {
    this.createForm();
  }

  //ERRORS APPEAR IN CONSOLE after INPUT into 'input text' in the dom

  // 1. create form group with entry inputText. Initialise it with no value 
  // and set the validation to a custom validator called 'count' that is 
  // located in the 'private scv: SyllableCountValidator' service
  // go to syllable-count.service file to see next steps
  createForm() {
    this.poemForm = this.fb.group({
          inputText: ['', [this.scv.count]],
      });
  }
}

syllable-count.service

    import { ValidatorFn, AbstractControl, FormControl } from '@angular/forms';
    import { forEach } from '@angular/router/src/utils/collection';
    import { Injectable } from '@angular/core';
    import { HttpClient, HttpResponse } from '@angular/common/http';
    import { Observable } from 'rxjs';

    @Injectable()
    export class SyllableCountValidator {

        constructor(private http: HttpClient) {}

        // 2. create http request to datamuse API that returns a syllable 
        // count repsonse for requested text
        public requestCount(text): Observable<Object> {
            return this.http.get(`https://api.datamuse.com/words?sp=${text}&md=s`) as Observable<Object>;
        }

        // 3. on any input into the formControl 'inputText' this will fire
        public count(control: FormControl) {

          // 4. split the string received from the fromControl 
          // 'textInput' into an array
          const arrayOfStrings = control.value.split(' ');

          // 5. define empty text variable
          let text = '';

          // 6. loop through step 3. arrayOfStrings and concatonate into 
          // correct format for http call and push to text variable in step 4.
          const pushToText = Array.prototype.forEach.call(arrayOfStrings, function(word) {
              text = `${text}+${word}`;
          });

          // 7. trim first character of text variable in step 4. to remove
          // unessecary '+' character
          const trimmedText = text.substr(1);

          // 8. call requestCount() in step 2. and return validation result
          if (trimmedText) {
            const validateSyllables = this.requestCount(trimmedText).subscribe((response) => {
              // 8.1.1 if syllable count is greater than 5 return error
              // of number of syllables it is greater by
              if ( response[0].numSyllables > 5 ) {
                return {
                  error: 5 - response[0].numSyllables
                }
              // 8.1.2 if syllable count is less than 5 return error
              // of number of syllables it is less by
              } else if ( response[0].numSyllables > 5 ) {
                return {
                  error: 5 - response[0].numSyllables
                }
              // 8.1.3 if syllable count equals 5 return validation
              // success as required by angular of 'null'
              } else if ( response[0].numSyllables == 5 ) {
                return null;
              }
            });

            // 9.2. return validation result
            return validateSyllables;      
          }
      }
    }
5
  • You have issue with "this" I will link you a fixed form in a few mins Commented May 29, 2018 at 19:39
  • stackblitz.com/edit/angular-custom-validator-issue-xbudgd Commented May 29, 2018 at 19:45
  • now just make sure you parse the object properly...the fix was to assign your method via a var and fat arrow function to make service provider's this transparent for the function Commented May 29, 2018 at 19:46
  • you are returning the result inside the subscribe callback, it doesn't make sense, Look at the documentation angular.io/guide/form-validation, basically, you should write a function which will return an AsyncValidatorFn, instead of calling subscribe you should use the map function an return the observable Commented May 29, 2018 at 20:02
  • thankyou I am a student so this helps hugely for advice on structure, i did read the angular documentation but it did not make that much sense to me, ill give what you advise a go Commented May 29, 2018 at 20:36

1 Answer 1

3

You have issue with 'this' that is not pointing to the proper scope.

"Easy" fix is to use fat arrow function in your service provider this way to ensure 'this' is pointing at the service provider:

instead of:

public count(control: FormControl) {

do:

public count = (control: FormControl) => {

Now your code still needs other issues addressed (like parsing of the response object is not valid).

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

Comments

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.