42

I have been trying to implement sign in with Google in angular 2 in a separate login component. I am unable to implement it with the documentation available in Google https://developers.google.com/identity/sign-in/web/sign-in

Google sign in does work when I declare my script tags and google callback function inside my index.html file. But I require a separate component to be able to render the sign in with google button and receive the callback in it to further process the access token which is received for a user

Mattew Eon
  • 1,722
  • 1
  • 21
  • 38
faris yousuf
  • 439
  • 1
  • 4
  • 7
  • Possible duplicate of [Google Sign-In for Websites and Angular 2 using Typescript](http://stackoverflow.com/questions/35530483/google-sign-in-for-websites-and-angular-2-using-typescript) – 030 Dec 22 '16 at 14:47
  • Another shorter solution found here: https://stackoverflow.com/questions/39880243/implementing-google-sign-in-button-in-angular2 – Daniel Nov 22 '17 at 18:20

7 Answers7

73

Add this line in your app index.html file

INDEX.html

<script src="https://apis.google.com/js/platform.js" async defer></script>

Component.ts file

declare const gapi: any;
  public auth2: any;
  public googleInit() {
    gapi.load('auth2', () => {
      this.auth2 = gapi.auth2.init({
        client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
        cookiepolicy: 'single_host_origin',
        scope: 'profile email'
      });
      this.attachSignin(document.getElementById('googleBtn'));
    });
  }
  public attachSignin(element) {
    this.auth2.attachClickHandler(element, {},
      (googleUser) => {

        let profile = googleUser.getBasicProfile();
        console.log('Token || ' + googleUser.getAuthResponse().id_token);
        console.log('ID: ' + profile.getId());
        console.log('Name: ' + profile.getName());
        console.log('Image URL: ' + profile.getImageUrl());
        console.log('Email: ' + profile.getEmail());
        //YOUR CODE HERE


      }, (error) => {
        alert(JSON.stringify(error, undefined, 2));
      });
  }

ngAfterViewInit(){
      this.googleInit();
}

Template html file

<button id="googleBtn">Google</button>

View the demo on Plunker

roberrrt-s
  • 7,914
  • 2
  • 46
  • 57
Pravesh Khatana
  • 1,034
  • 1
  • 11
  • 18
  • You have helped me greatly by introducing me on how to access the component object within the callback. Using 'this' was not working... But using 'that' is the answer! – Qyaffer Feb 21 '17 at 20:32
  • 1
    Thanks :) Quaffer and @Steel for your extra input. – Pravesh Khatana Mar 02 '17 at 16:57
  • 1
    btw. there is no angularJS 2. There is Angular which is Angular 2+, and there is AngularJS which is Angular below 2 – bartosz.baczek Apr 14 '17 at 13:41
  • Right..AngularJS is JavaScript based and Angular2+ is Typescript – Pravesh Khatana Apr 15 '17 at 03:02
  • 1
    Hi Pravesh ,your code works fine but i am getting the error as 'popup closed by the user'.So I am not able to get the response.Can you suggest any help. – MMR May 12 '17 at 12:56
  • 1
    your code works fine. But after login i want to redirect on home page. how do i achieve this. CODE::> this._router.navigate(['/home']); // not working – Hardik Patel Jul 28 '17 at 13:21
  • having same issue than @MMR – Ninja Coding Aug 09 '17 at 05:08
  • The is one thing that I still can't figure out. All Goole OAuth-Angular examples uses gapi dirrectly. `
    ` is no longer used and no longer works. (https://developers.google.com/identity/sign-in/web/) I'm still trying to avoid using gapi dirrectly and can't figure out how to specify onSignIn handler in ts files that will be triggered. If I do this in additional asset/client.js file (without angular) - this works fine
    – Dmitry Gusarov Dec 16 '17 at 17:13
  • This post helped me a lot but I would like to add something. Sine we use attachClickHandler and we are out of the ngZone I faced several issues with the update of the UI. This is why I had to use the following code in order to update the UI accordingly: constructor(private _ngZone: NgZone ) { .... this.auth2.attachClickHandler(element, {}, (googleUser) => { this._ngZone.run(() => { ..... – Investigator Oct 01 '18 at 13:37
  • 1
    This code works, but the button is dead if user clicks back in browser or during some other routing issues. – Dalibor Dec 04 '18 at 08:32
  • Sorry I missed the point in last post: after user browses application, during routing wierd stuff happens, he is either not redirected, or app waits for minutes before redirecting him.. etc. I had same issue when implementing FB login, and I solved it with: https://www.npmjs.com/package/ngx-facebook. I need to find corresponding solution for Google OAuth – Dalibor Dec 05 '18 at 09:20
  • hi Pravesh Khatana, I am getting error message after using this code "Invalid cookiePolicy". You have any idea to fix it? – Sandip Moradiya Mar 08 '19 at 09:39
  • Hi, It looks like cookiepolicy doesn't work but cookie_policy. – Ahadu Melesse Jan 24 '20 at 21:22
  • 1
    And I am getting an error, ERROR TypeError: Cannot read property 'load' of undefined – Ahadu Melesse Jan 24 '20 at 21:26
  • I have the same issue "Cannot read property 'load' of undefined". It looks like the platform.js is not loaded by the time ngAfterViewInit() because it is "async defer". I suggest removing those properties from – BlackOverlord Oct 11 '20 at 16:25
  • That's Work for me. – Mayur Kukadiya Dec 10 '20 at 09:06
19

src/index.html

In the index.html file of your app you need to add this in the <head> section:

<meta name="google-signin-scope" content="profile email">
<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>

typings/browser/ambient/gapi/

You need to add gapi & gapi.auth2 to your typings:

npm install --save @types/gapi.auth2
npm install --save @types/gapi

(see this borysn's question to understand this a little better).

src/app/+login/login.component.ts

This is the file of my component, here you need to use the ngAfterViewInit() to use the gapi an get the auth. And you can follow the implementation here developers.google...sign-in/web/build-button

As an example, this is my template:

<div id="my-signin2"></div>

and sign in function:

ngAfterViewInit() {
    gapi.signin2.render('my-signin2', {
        'scope': 'profile email',
        'width': 240,
        'height': 50,
        'longtitle': true,
        'theme': 'light',
        'onsuccess': param => this.onSignIn(param)
    });
}

public onSignIn(googleUser) {
    var user : User = new User();

    ((u, p) => {
        u.id            = p.getId();
        u.name          = p.getName();
        u.email         = p.getEmail();
        u.imageUrl      = p.getImageUrl();
        u.givenName     = p.getGivenName();
        u.familyName    = p.getFamilyName();
    })(user, googleUser.getBasicProfile());

    ((u, r) => {
        u.token         = r.id_token;
    })(user, googleUser.getAuthResponse());

    user.save();
    this.goHome();
};

UPDATE: After some time, and taking in consideration the comments, this answer needed a small update.

Gatsbimantico
  • 1,822
  • 15
  • 32
  • 2
    You can also use `npm install --save @types/gapi.auth2` `npm install --save @types/gapi` Instead of manually adding the namespaces to your typings I don't know if this is what you meant, I'm just starting with typescript and angular and found this way of adding types – Gustavo Nov 03 '16 at 19:16
  • 7
    I played with that example and realized that `(data-onsuccess)="onSignIn"` in the template is not used. Registering the callback is done in `ngAfterViewInit`. The callback does not trigger the rerendering of the template anyways (I was additionally printing the logged in profile in the template), because the call happens outside of Angular zone. Make sure to inject NgZone and wrap the updates with with `zone.run(() => { /* Updates here */})`. – TomaszGuzialek Nov 11 '16 at 15:21
  • 1
    This will never get fired `(data-onsuccess)="onSignIn"` If anyone got it to work, I would be happy to read the implementation. Thanks. – mrgoos Jan 06 '17 at 13:00
  • 2
    What if I don't want to add the googleClientId in the meta tag? – Smit Aug 12 '17 at 08:28
  • 1
    @Smit, public googleClientId is safe and efficient practice. If you want to hide it for some reason, you have to delegate authentication communication back to your sever, adding more requests and delays. With public googleClientId all this job is done between client browser and google api without involving your server at all. Only your guarded API calls would pass the JWT token and server can validate it (also without extra communication to google). – Dmitry Gusarov Dec 16 '17 at 15:00
16

Lexical scoping with an arrow (=>) function makes the use of let that = this; unnecessary.

A cleaner version of Pravesh's example, without the need for the that scoping work-around, would be:

Index.html

<script src="https://apis.google.com/js/platform.js" async defer></script>

Component.ts

declare const gapi: any;

@Component({
  selector: 'google-signin',
  template: '<button id="googleBtn">Google Sign-In</button>'
})
export class GoogleSigninComponent implements AfterViewInit {

  private clientId:string = 'YOUR_CLIENT_ID.apps.googleusercontent.com';

  private scope = [
    'profile',
    'email',
    'https://www.googleapis.com/auth/plus.me',
    'https://www.googleapis.com/auth/contacts.readonly',
    'https://www.googleapis.com/auth/admin.directory.user.readonly'
  ].join(' ');

  public auth2: any;

  public googleInit() {        
    gapi.load('auth2', () => {
      this.auth2 = gapi.auth2.init({
        client_id: this.clientId,
        cookiepolicy: 'single_host_origin',
        scope: this.scope
      });
      this.attachSignin(this.element.nativeElement.firstChild);
    });
  }

  public attachSignin(element) {
    this.auth2.attachClickHandler(element, {},
      (googleUser) => {
        let profile = googleUser.getBasicProfile();
        console.log('Token || ' + googleUser.getAuthResponse().id_token);
        console.log('ID: ' + profile.getId());
        // ...
      }, function (error) {
        console.log(JSON.stringify(error, undefined, 2));
      });
  }

  constructor(private element: ElementRef) {
    console.log('ElementRef: ', this.element);
  }

  ngAfterViewInit() {
    this.googleInit();
  }
}

Template

<div id="googleBtn">Google</div>

Working Plnkr

Steve
  • 11,596
  • 7
  • 39
  • 53
8

There is also another way to connect with google :

Add theses lines in the index.html :

<meta name="google-signin-client_id" content="YOUR-GOOGLE-APP-ID.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js"></script>

and then here is a sample code to write on a component (or a service if you want) :

import {Component} from "@angular/core";
declare const gapi : any;


@Component({ ... })
export class ComponentClass {
   constructor() {
      gapi.load('auth2', function () {
         gapi.auth2.init()
      });
   }

   googleLogin() {
      let googleAuth = gapi.auth2.getAuthInstance();
      googleAuth.then(() => {
         googleAuth.signIn({scope: 'profile email'}).then(googleUser => {
            console.log(googleUser.getBasicProfile());
         });
      });
   }
}
Mattew Eon
  • 1,722
  • 1
  • 21
  • 38
8

As of now, the angular latest version came and mostly we are using angular 4/5/6, so thought to give this simple solution to login by social so someone who really want it

Angular 4/5/6 Social Login

In your AppModule, import the SocialLoginModule

import { SocialLoginModule, AuthServiceConfig } from "angularx-social-login";
import { GoogleLoginProvider, FacebookLoginProvider, LinkedInLoginProvider} from "angularx-social-login";


let config = new AuthServiceConfig([
  {
    id: GoogleLoginProvider.PROVIDER_ID,
    provider: new GoogleLoginProvider("Google-OAuth-Client-Id")
  },
  {
    id: FacebookLoginProvider.PROVIDER_ID,
    provider: new FacebookLoginProvider("Facebook-App-Id")
  },
  {
    id: LinkedInLoginProvider.PROVIDER_ID,
    provider: new FacebookLoginProvider("LinkedIn-client-Id", false, 'en_US')
  }
]);

export function provideConfig() {
  return config;
}

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
    SocialLoginModule
  ],
  providers: [
    {
      provide: AuthServiceConfig,
      useFactory: provideConfig
    }
  ],
  bootstrap: [...]
})
export class AppModule { }

And use it in your components

by importing below modules

import { AuthService } from "angularx-social-login";
import { SocialUser } from "angularx-social-login";

For complete reference you can look their Github

it has really simple page for demo

abdulbarik
  • 6,101
  • 5
  • 38
  • 59
  • Only this solution worked with Angular 6 with npm package angular5-social-login – Manish Jain Jun 14 '18 at 23:37
  • I have implemented the above and its working fine. I need to call https://accounts.google.com/logout, for logging user out of google completely. Since AuthService will make the instance null and log me just of the app, my requirement demands logging out of google. How should i achieve the same using the link i have mentioned i.e. https://accounts.google.com/logout – Rahul Sharma Aug 28 '18 at 12:44
  • As we doesn’t have back end service so this service is only manage front end.You can do manually, check the local storage,you’ll get something or use a flag when logged in and save it to local storage and on logged out delete from there. – abdulbarik Aug 28 '18 at 14:25
  • How do I implement it if I get "Google-OAuth-Client-Id" dynamically from service? I spent a day trying, but no luck. – Dalibor Dec 05 '18 at 15:18
7

pretty much none of this worked for me because I want the google button made by Google. @mathhew eon's code worked but that does not use their button.

so I threw the google data-success function on the window and it works PERFECT! It also has the added advantage of if the user is already logged in it will auto call the googleLogin function.

html

<div class="g-signin2" data-onsuccess="googleLogin" data-theme="dark"></div>

In your index.html put this in the head:

<meta name="google-signin-client_id" content="YOUR-GOOGLE-APP-ID.apps.googleusercontent.com">
<meta name="google-signin-scope" content="profile email AND WHATEVER OTHER SCOPES YOU WANT">
<script src="https://apis.google.com/js/platform.js" async defer></script>

then in your ngOnInit

ngOnInit() {
    (window as any).googleLogin = this.googleLogin
}
public googleLogin(userInfo) {
    console.log(userInfo)
}
Samuel Thompson
  • 2,429
  • 4
  • 24
  • 34
2

Everything is the same in previous answered except I added

declare var gapi: any; otherwise, you will get the error.

src/index.html

In the index.html file of your app you need to add this in the section:

<meta name="google-signin-scope" content="profile email">
<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>

typings/browser/ambient/gapi/

You need to add gapi & gapi.auth2 to your typings:

npm install --save @types/gapi.auth2
npm install --save @types/gapi

(see this borysn's question to understand this a little better).

src/app/+login/login.component.ts

This is the file of my component, here you need to use the ngAfterViewInit() to use the gapi an get the auth. And you can follow the implementation here developers.google...sign-in/web/build-button

As an example, this is my template:

<div id="my-signin2"></div>

and sign in function:

 declare var gapi: any;

ngAfterViewInit() {
   gapi.signin2.render('my-signin2', {
      'scope': 'profile email',
      'width': 240,
      'height': 50,
      'longtitle': true,
      'theme': 'light',
      'onsuccess': param => this.onSignIn(param)
  });
}

public onSignIn(googleUser) {
   var user : User = new User();

      ((u, p) => {
         u.id            = p.getId();
         u.name          = p.getName();
         u.email         = p.getEmail();
         u.imageUrl      = p.getImageUrl();
         u.givenName     = p.getGivenName();
         u.familyName    = p.getFamilyName();
      })(user, googleUser.getBasicProfile());

      ((u, r) => {
         u.token         = r.id_token;
      })(user, googleUser.getAuthResponse());

      user.save();
      this.goHome();
};
Ankit Kumar Gupta
  • 1,256
  • 1
  • 10
  • 23
  • 1
    Not everything is the same except gapi. You also added render method on gapi.singin2 which was a saver for me. Thanks. – IvanMih Apr 17 '20 at 09:40