Google ReCaptcha v2 and Angular

Note: This was done for Angular 1, but Angular 2 is the same except the bit at the end of the post.

Google ReCaptcha

In order to include Google ReCaptcha in your website you need a Google account and get into the “Google ReCaptcha” web page. Once in there create a site and add your domain/s in there, recaptcha generates a key that’s only valid for the included domains, and that affects localhost too, so in order to test in localhost you must include that domain in the list.

As it would be a bad approach to use production keys to test in localhost domain (anyone could make use of your keys) the best approach is to not include localhost into your production site and create a different one with the localhost domain (plus 127.0.0.1) in it. You don’t even need your client/company keys, just using your own Google Account with a localhost domain to get a key will work on any local site.

Displaying ReCaptcha

The easiest way to do so is by just adding the HTML codes Google gives you, one to call the script and the other as an HTML element that includes the key and the script will use to include the captcha. Unfortunately Angular is a framework to create SAPs, and due to the dynamism of such approach you’ll probably find yourself in one of these two problems when loading the captcha explicitly:

1. That Google Recaptcha script doesn’t load its dependencies in time and Angular attempts to call recaptcha before it’s ready, getting an undefined exception.
2. That Angular doesn’t load in time and as you possibly require to set the key into the HTML element dynamically it crashes as it isn’t yet there with an “invalid key” exception.

Or in other words, before loading the captcha you need both GoogleRecaptcha and your Angular app to be ready.

How to tell Angular when ReCaptcha is ready

Google Recaptcha v2 already comes with a way to tell when it’s ready, you basically need to call it using a querystring param that gives it a global javascript function to call back once it’s ready:

<script type="text/javascript">
    var global_isRecaptchaReady = false;
    var gRecaptchaOnLoad = function () {
        global_isRecaptchaReady = true;
    };
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=gRecaptchaOnLoad&render=explicit" async defer></script>

Just make sure you give them names that won’t ever conflict with anything else, I think the ones I gave won’t. As that variable is declared as global Angular can access it, though the safest way to do so is this one:

static $inject = ['$scope', '$window']; // make sure to include window!
constructor(
   private $scope: ng.IScope,
   public $window
   ) {
      if (this.$window.global_isRecaptchaReady) { ... }
   }

Though, in case is not ready, best approach would be to wait until it is, I chose to just use a timeout to try again asap, but you can use your own approach. Basically, Google will not try to load the captcha on its own as the script has the “render=explicit” param on the call, and your Angular app won’t try to load it before is ready as you will be checking if it’s ready or not.

        private renderCaptcha() {
            if (this.$window.global_isRecaptchaReady) {
                grecaptcha.render("request-demo-recaptcha", { 'sitekey': this.recaptchaSiteKey });
            } else {
                // Sometimes grecaptcha needs a bit more of time to load, so wait and recall:
                this.$timeout(() => { this.renderCaptcha(); }, 200); // Note: Has to be called like this or loses scope and cant access $window
            }
        }

Angular 2

Same for Angular 2, except there are no window or timeout services, so just use this inside the component:

    private renderCaptcha() {
        var grecaptcha: any = window["grecaptcha"];
        
        // Note: Sometimes grecaptcha needs a bit more of time to load, so wait and recall.
        // Dont try to check if recaptcha is not null, it may be there but not ready, use the global var
        if (window["global_isRecaptchaReady"] == false) {
            setTimeout(() => { this.renderCaptcha(); }, 200);
        } else {
            grecaptcha.render("request-demo-recaptcha", { 'sitekey': this.recaptchaSiteKey });
        }
    }

Additional Info

Google documentation on how to set up version 2 ReCaptcha.

Leave a Reply

Your email address will not be published. Required fields are marked *