Milan Ferus-Comelo
Milan Ferus-Comelo

Reputation: 740

Using Google One Tap in Angular

I'd like to use Google One Tap in my Angular 11 app. Following the documentation I added <script async defer src="https://accounts.google.com/gsi/client"></script> to my html and then used the following code in my app.component.html:

<div id="g_id_onload"
    data-client_id="MY_GOOGLE_CLIENT_ID"
    data-callback="handleCredentialResponse",
    data-cancel_on_tap_outside="false">
</div>

The popup works fine, though I can't seem to log in. If I create a function handleCredentialResponse in app.component.ts, I get the following error: [GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored.

If I instead try to use the JavaScript API, Typescript throws the following error: Property 'accounts' does not exist on type 'typeof google'

What should I do to be able to using Google One Tap in Angular?

Upvotes: 7

Views: 7615

Answers (4)

yudhishtir gaddipati
yudhishtir gaddipati

Reputation: 81

set the div in template to be rendered in ngOnInit

    `<div id="loginBtn" > </div>`

dynamically inject script tag in your login.ts as follows

constructor(private _renderer2: Renderer2, @Inject(DOCUMENT) private _document: Document){}

ngAfterViewInit() {
        const script1 = this._renderer2.createElement('script');
        script1.src = `https://accounts.google.com/gsi/client`;
        script1.async = `true`; 
        script1.defer = `true`; 
    this._renderer2.appendChild(this._document.body, script1);
  }

ngOnInit(): void { 
    // @ts-ignore
    window.onGoogleLibraryLoad = () => {  
      // @ts-ignore
      google.accounts.id.initialize({
        client_id: '335422918527-fd2d9vpim8fpvbcgbv19aiv98hjmo7c5.apps.googleusercontent.com',
        callback: this.googleResponse.bind(this),
        auto_select: false,
        cancel_on_tap_outside: true,  
      })
      // @ts-ignore
      google.accounts!.id.renderButton( document!.getElementById('loginBtn')!, { theme: 'outline', size: 'large', width: 200 } )
      // @ts-ignore
      google.accounts.id.prompt(); 
    }
  }


async googleResponse(response: google.CredentialResponse) { 
        // your logic goes here
}

Upvotes: 1

Nandha MV
Nandha MV

Reputation: 469

I too had the same problem in adding the function to the angular component. Then i found a solution by adding JS function in appComponent like this:

(window as any).handleCredentialResponse = (response) => {
      /* your code here for handling response.credential */
}

Hope this help!

Upvotes: 1

who_khiers
who_khiers

Reputation: 121

Google One Tap js library tries to find callback in the global scope and can't find it, because your callback function is scoped somewhere inside of your app, so you can attach your callback to window, like window.callback = function(data) {...}. Also, since you are attaching it to window, it's better to give the function a less generic name.

Upvotes: 0

maya.js
maya.js

Reputation: 1148

I had a similar problem when I used the HTML API approach, so I ended up using the JavaScript API instead.

Here's what I did:

First, make sure to install the @types/google-one-tap package.

As you mentioned, I'm also importing the script in my index.html file, like so:

<body>
  <script src="https://accounts.google.com/gsi/client" async defer></script>
  <app-root></app-root>
</body>

Now, moving on to your main component which in my case is app.component.ts, import the following first:

import { CredentialResponse, PromptMomentNotification } from 'google-one-tap';

Then, you can add this on the ngOnInit(). Make sure to read the documentation to get more details on the onGoogleLibraryLoad event:

// @ts-ignore
window.onGoogleLibraryLoad = () => {
  console.log('Google\'s One-tap sign in script loaded!');

  // @ts-ignore
  google.accounts.id.initialize({
    // Ref: https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
    client_id: 'XXXXXXXX',
    callback: this.handleCredentialResponse.bind(this), // Whatever function you want to trigger...
    auto_select: true,
    cancel_on_tap_outside: false
  });

  // OPTIONAL: In my case I want to redirect the user to an specific path.
  // @ts-ignore
  google.accounts.id.prompt((notification: PromptMomentNotification) => {
    console.log('Google prompt event triggered...');

    if (notification.getDismissedReason() === 'credential_returned') {
      this.ngZone.run(() => {
        this.router.navigate(['myapp/somewhere'], { replaceUrl: true });
        console.log('Welcome back!');
      });
    }
  });
};

Then, the handleCredentialResponse function is where you handle the actual response with the user's credential. In my case, I wanted to decode it first. Check this out to get more details on how the credential looks once it has been decoded: https://developers.google.com/identity/gsi/web/reference/js-reference#credential

handleCredentialResponse(response: CredentialResponse) {
// Decoding  JWT token...
  let decodedToken: any | null = null;
  try {
    decodedToken = JSON.parse(atob(response?.credential.split('.')[1]));
  } catch (e) {
    console.error('Error while trying to decode token', e);
  }
  console.log('decodedToken', decodedToken);
}

Upvotes: 6

Related Questions