The Client Side

Initialization

First, let’s create the directive.

We have three inputs, the public key, a config object for which we have defined an interface, and an optional user’s language.

Our next step is to define the onloadCallback. This function will get called when all the dependencies have loaded.

Before we do this, let’s create a method that will insert the reCAPTCHA script.

The code is straightforward. Insert the Javascript resource, setting the onload parameter to the name of our onload callback function and the render parameter to explicit .

Now we can implement the onload callback ( reCaptchaLoad ).

When the onload callback is executed, we can render the container as a reCAPTCHA widget by calling the grecaptcha.render() method with the DOM element and the config object.

Type Definitions

At this point, typescript will yell at you because you don’t have type definitions for the grecaptcha and the reCaptchaLoad objects. A quick fix will be to do the following:

Create Custom Form Control

As you may know, if you want to create custom form control in Angular, you need to implement the ControlValueAccessor interface. I’m not going to go into this topic because it requires an article of its own. You can read more about the subject here.

We have implemented the three methods that the ControlValueAccessor require. In our case, we don’t care about the writeValue() method, so we leave it empty.

Now let’s go back for a second. You remember we defined two callbacks in the final config object?

'callback': this.onSuccess.bind(this),

'expired-callback': this.onExpired.bind(this)

callback is the name of your callback function to be executed when the user submits a successful CAPTCHA response.

expired-callback the name of your callback function to be executed when the recaptcha response expires and the user needs to solve a new CAPTCHA.

We need to notify the formControl that it’s valid if we get the token from the onSuccess function or that it’s invalid if the onExpired function is called.

💣, surprise! We come across one of Angular’s unusual cases, in which Angular is unconscious and therefore does not run the change detection. Angular doesn’t know about our global callback. Therefore, we need to wrap our code in the zone.run() method.

Running functions via run allows you to reenter Angular zone from a task that was executed outside of the Angular zone

Add Validators Programmatically

Remember when I said at first that I wanted everything to be as transparent as possible to the user? This is one of the cases.

We need to add the required validation to the form control. We can accomplish this by injecting the NgControl into our directive to get the control instance.

But there is one problem, we can’t inject the NgControl into our directive because we have implemented the directive as a ControlValueAccessor and because of that Angular will throw an error:

Cannot instantiate cyclic dependency! NgControl

We can get around this problem by getting the NgControl instance directly from the directive Injector .

😎 Now we have the control instance and we can add the required validator programmatically. We also need to call the updateValueAndValidity() method because calling the setValidators() doesn’t trigger any update or value change event.

The Server Side

When the user solves a reCAPTCHA, we also need to verify the token with the Google API to ensure that the token is valid. The required parameters are the secret key and the token. Let’s use express for this task.

The response will be:

{

"success": true|false,

"challenge_ts": timestamp,

"hostname": string

"error-codes": [...]

}

In our case, we only care about the success key. Let’s add an async validator that will send the HTTP request to our endpoint with the token and will set the control status based on the response. I’m going to use the DI for getting the endpoint from our consumer.

I already wrote a dedicated article on how to implement async validators in Angular, you can find it here. In a nutshell, if we have a success response we are returning null to notify the control that it’s valid otherwise we are returning the error object.

We also need to add the validator to the directive providers —

@Directive({

selector: '[nbRecaptcha]',

providers: [ ReCaptchaAsyncValidator ]

})

And to provide the endpoint in our signin component —

@Component({

...

providers: [{

provide: RECAPTCHA_URL,

useValue: 'http://localhost:3000/validate_captcha'

}]

})

export class SigninComponent { }

The last thing is to trigger the validation in our reCAPTCHA directive.

When we get a valid token, we need to add the async validator to the control validators that will trigger our HTTP request and based on the response will set the control status.

You can find the full source code with a few add-ons, here.

Follow me on Medium or Twitter to read more about Angular, Vue and JS!