Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best way to use ng-recaptcha with multiple forms #35

Closed
Toub opened this issue May 10, 2017 · 10 comments
Closed

Best way to use ng-recaptcha with multiple forms #35

Toub opened this issue May 10, 2017 · 10 comments

Comments

@Toub
Copy link

Toub commented May 10, 2017

Hi have 2 components containing form on the same page, and I want to use invisible recaptcha in both of them.

I tried to use it twice, one recaptcha per component, and it looks to work almost, but I frequently get this JS error:

TypeError: null is not an object (evaluating 'a.style')
  at Th (/recaptcha/api2/r20170503135251/recaptcha__fr.js:130:152)
  at None (/recaptcha/api2/r20170503135251/recaptcha__fr.js:379:259)
  at None ([native code])
  at r (/vendor.e8ca299bdc7eb8a082ad.bundle.js:533:54252)
  at runTask (/polyfills.e973013abdce7c05a842.bundle.js:36:3206)

So, I guess it is probably better to create the recaptcha in my main component, then get a reference to it using @ViewChild, and pass it to each of my sub-components as an Input().

And when submitting a form in one of my component, manually trigger:

this.recaptchaRef.execute();

But infortunately, this method does not return an observable nor provide any callback mechanism, and make it tricky to execute the form action in my sub-component once the captcha has been resolved.

Any suggestion to solve my issue, or any plan to support such a scenario?

@DethAriel
Copy link
Owner

Hi, @Toub !

As to the error that you mentioned - it's very hard to navigate through webpack-uglified vendor bundle, could you provide a non-obfuscated stack trace for further investigation?

As to how you can solve the issue. Since you already have the recaptchaRef, you can use the resolved event emitter:

this.recaptchaRef.resolved.subscribe((response) => /* stuff */);
this.recaptchaRef.execute();

Would that suffice?

@DethAriel
Copy link
Owner

Labelling this as "question" until more info on the error is provided. Will convert to "bug" if the error occurs in ng-recaptcha code

@Toub
Copy link
Author

Toub commented May 22, 2017

This error only occurs sometimes in production, so I can't get a better stacktrace.

I tried locally your solution, and it looks good, so I will deploy in to production and let you know if it works.

If yes, it could be good to document the solution.

Here is what I did:

Main component

<!-- AccountLogin.html -->
<!-- declare captcha -->
<re-captcha #recaptchaRef="reCaptcha" size="invisible" siteKey="***"></re-captcha>

<!-- pass a reference to sub-components -->
<tw-account-reset-form [recaptchaRef]="recaptchaRef"></tw-account-reset-form>
<tw-account-create-form [recaptchaRef]="recaptchaRef"></tw-account-create-form>
// AccountLogin.ts
import {RecaptchaComponent} from 'ng-recaptcha';

export class AccountLogin {

   @ViewChild('recaptchaRef')
   recaptchaRef: RecaptchaComponent;

Sub form 1 & 2

// AccountResetForm.ts
import {RecaptchaComponent} from 'ng-recaptcha';

export class AccountResetForm {
   @Input()
  recaptchaRef: RecaptchaComponent;

   captchaResolved(){
      // form submitted, do something
   }
   tryToSubmit() {
      this.recaptchaRef.resolved.first().subscribe((captchaResponse) => this.captchaResolved(captchaResponse), err => {
          console.log('[AccountSigninEmail] Error resolving captcha', err);
        }, () => {
          // finally
          this.recaptchaRef.reset();
        });
        this.recaptchaRef.execute()
   }
}

I guess it is important to subscribe to the first emitted item only, and also to reset the component in the final block.

Also, when the first visible captcha appeared, I got the following error locally (not sure it can help!):

TypeError: a.la is null
Vp()
 recaptcha__fr.js:393
Np.prototype.Hk()
 recaptcha__fr.js:394
[1781]/</</ZoneDelegate.prototype.invokeTask()
 polyfills.bundle.js:7597
[1781]/</</Zone.prototype.runTask()
 polyfills.bundle.js:7364
ZoneTask/this.invoke()
 polyfills.bundle.js:7659
timer()

But there is no side-effect for the user, as the captcha shows and works correctly.

@DethAriel
Copy link
Owner

@Toub could you please re-verify the issue? It could have been resolved by a recaptcha update, as mentioned in #38 (comment)

@Toub
Copy link
Author

Toub commented Jun 6, 2017

I updated and published to prod, but I still see the following issue in the logs reported by sentry tracker (not sure if it is related to the fact I have multiple forms):

TypeError: Cannot read property 'style' of null
  at Ih (/recaptcha/api2/r20170531093331/recaptcha__fr.js:122:430)
  at Qo (/recaptcha/api2/r20170531093331/recaptcha__fr.js:389:41)
  at vp.Ko.Jb (/recaptcha/api2/r20170531093331/recaptcha__fr.js:388:283)
  at Kp.f.Hi (/recaptcha/api2/r20170531093331/recaptcha__fr.js:404:180)
  at km.<anonymous> (/recaptcha/api2/r20170531093331/recaptcha__fr.js:236:367)

It could also be related to a network problem loading google captcha itself.

@DethAriel
Copy link
Owner

@Toub it looks like reCAPTCHA is being loaded successfully. I assume that you are right, and this error indeed appears due to the fact that there are multiple reCAPTCHA components on a page. reCAPTCHA seems to have problems with such a pattern. Since you haven't been able to reproduce the issue in DEV environment it's hard to tell whether it has an impact on the end user or not. Unfortunately, there's little I can do at this point to help mitigate this. My suggestion would be to refactor two forms in such a way that there is at maximum one recaptcha shown.

@Bodeclas
Copy link

Bodeclas commented Dec 15, 2017

Hello, I have the same problem with the two forms and the solution that implements the next, to see what you think.

// service.ts
@Injectable()
export class RecaptchaService {
  private recaptchaSource = new BehaviorSubject<Boolean>(false);
  getStatus = this.recaptchaSource.asObservable();

  constructor() {}

  public setStatus(option: Boolean) {
    this.recaptchaSource.next(option);
  }
}
// child component.ts
@Output() submit: EventEmitter<any> = new EventEmitter<any>();
// parent component.ts

  @ViewChild('recaptchaRef') recaptchaRef: RecaptchaComponent;

  recaptchaStatus: Boolean = false;

  constructor(private recaptchaService: RecaptchaService) { }

  ngOnInit() {
    //subscribe to recaptcha service
    this.recaptchaService.getStatus.subscribe(status => {
      this.recaptchaStatus = status;
      console.log(status);
    });
  }

  ngAfterViewInit() {
    console.log(`ngAfterViewInit`, this.recaptchaRef);

    //if recaptcha is resolved then send HTTP request!
    this.recaptchaRef.resolved.first().subscribe(
      captchaResponse => {
        this.recaptchaService.setStatus(true);

        console.log(`Captcha valid, sending HTTP request...`);
      },
      err => {
        console.log('[AccountSigninEmail] Error resolving captcha', err);
        //set false to recaptcha status
        this.recaptchaService.setStatus(false);
      },
      () => {
        //finally reset reCaptcha
        this.recaptchaRef.reset();
      }
    );
  }

  onSubmit(event) {
    //on Submit execute recaptchaRef
    this.recaptchaRef.execute();
  }

@ch-hristov
Copy link

@Bodeclas hi, did you manage to solve it?

@Bodeclas
Copy link

@ch-hristov hi! yes, I did.

@Bodeclas
Copy link

@ch-hristov

recaptcha.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class RecaptchaService {
  private recaptchaSource = new BehaviorSubject<Boolean>(false);
  getStatus = this.recaptchaSource.asObservable();

  constructor() { }

  public setStatus(option: Boolean) {
    this.recaptchaSource.next(option);
  }
}

sign-up-layout.html

      <mat-tab-group>
        <mat-tab>
           <app-form-user></app-form-user>
        </mat-tab>
        <mat-tab>
           <app-form-company></app-form-company>
        </mat-tab>
      </mat-tab-group>

      <!-- ReCaptcha invisible -->
      <re-captcha size="invisible" #recaptchaRef="reCaptcha"></re-captcha>
import { RecaptchaComponent } from 'ng-recaptcha';
import { RecaptchaService } from '@core/services';
@ViewChild('recaptchaRef') recaptchaRef: RecaptchaComponent;
...
class SignUpLayout
...

  ngAfterViewInit() {
    this.recaptchaRef.resolved.subscribe((response) => {
      // Captcha valid
      this.recaptchaService.setStatus(true);
    }, error => {
      console.error('[AccountSigninEmail] Error resolving captcha');
      this.recaptchaService.setStatus(false);
      this.recaptchaRef.reset();
    });
  }

  onSubmit(event) {
    console.log(event);
    if (!this.recaptchaStatus) {
      // If recatpcha status is not resolved then execute recaptchaRef
      this.recaptchaRef.execute();
    }
  }

...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants