Published:

Angular + Facebook - How to use the Facebook SDK in an Angular App

Other versions available:

This is a quick post to go through the steps to load, initialize and use the Facebook JS SDK in an Angular application.

The below code snippets are from an Facebook login tutorial I posted yesterday, for the full tutorial and live demo of the code see Angular 10 - Facebook Login Tutorial & Example.


Load the Facebook SDK in an Angular App Initializer

The app initializer runs before the Angular app starts up to load and initialize the Facebook SDK, and automatically log the user into the Angular app if they are already logged in with Facebook.

When an app initializer function returns a Promise like this one, the Angular app waits for the promise to be resolved before it starts, so you can be sure that the Facebook SDK is initialized and the FB object is ready to be used as soon as the Angular app starts.

The app initializer is added to Angular app in the providers section of the app module (/src/app/app.module.ts) using the APP_INITIALIZER injection token. For more info see https://angular.io/api/core/APP_INITIALIZER.

import { AccountService } from '@app/_services';
import { environment } from '@environments/environment';

export function appInitializer(accountService: AccountService) {
    return () => new Promise(resolve => {
        // wait for facebook sdk to initialize before starting the angular app
        window['fbAsyncInit'] = function () {
            FB.init({
                appId: environment.facebookAppId,
                cookie: true,
                xfbml: true,
                version: 'v8.0'
            });

            // auto authenticate with the api if already logged in with facebook
            FB.getLoginStatus(({authResponse}) => {
                if (authResponse) {
                    accountService.apiAuthenticate(authResponse.accessToken)
                        .subscribe()
                        .add(resolve);
                } else {
                    resolve();
                }
            });
        };

        // load facebook sdk script
        (function (d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) { return; }
            js = d.createElement(s); js.id = id;
            js.src = "https://connect.facebook.net/en_US/sdk.js";
            fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));    
    });
}


Add TypeScript type definitions for the Facebook SDK

To add TypeScript support for the Facebook FB object and its methods follow these two steps.

1. Add the type definitions from @types/facebook-js-sdk with the following npm command:

npm install --save @types/facebook-js-sdk


2. Then add "facebook-js-sdk" to the "types" array in your tsconfig.app.json file:

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
    "extends": "./tsconfig.base.json",
    "compilerOptions": {
        "outDir": "./out-tsc/app",
        "types": ["facebook-js-sdk"]
    },
    "files": [
        "src/main.ts",
        "src/polyfills.ts"
    ],
    "include": [
        "src/**/*.d.ts"
    ]
}


Use the Facebook SDK in your Angular App!

You're now ready to use the Facebook SDK in your Angular app, as an example here is the account service (/src/app/_services/account.service.ts) from the Facebook login example project, it uses some Facebook SDK auth methods on lines 43, 63 and 110.

import { Injectable } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, from, of, EMPTY } from 'rxjs';
import { map, concatMap, finalize } from 'rxjs/operators';

import { environment } from '@environments/environment';
import { Account } from '@app/_models';

const baseUrl = `${environment.apiUrl}/accounts`;

@Injectable({ providedIn: 'root' })
export class AccountService {
    private accountSubject: BehaviorSubject<Account>;
    public account: Observable<Account>;

    constructor(
        private router: Router,
        private route: ActivatedRoute,
        private http: HttpClient
    ) {
        this.accountSubject = new BehaviorSubject<Account>(null);
        this.account = this.accountSubject.asObservable();
    }

    public get accountValue(): Account {
        return this.accountSubject.value;
    }

    login() {
        // login with facebook then authenticate with the API to get a JWT auth token
        this.facebookLogin()
            .pipe(concatMap(accessToken => this.apiAuthenticate(accessToken)))
            .subscribe(() => {
                // get return url from query parameters or default to home page
                const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
                this.router.navigateByUrl(returnUrl);
            });
    }

    facebookLogin() {
        // login with facebook and return observable with fb access token on success
        return from(new Promise<fb.StatusResponse>(resolve => FB.login(resolve)))
            .pipe(concatMap(({ authResponse }) => {
                if (!authResponse) return EMPTY;
                return of(authResponse.accessToken);
            }));
    }

    apiAuthenticate(accessToken: string) {
        // authenticate with the api using a facebook access token,
        // on success the api returns an account object with a JWT auth token
        return this.http.post<any>(`${baseUrl}/authenticate`, { accessToken })
            .pipe(map(account => {
                this.accountSubject.next(account);
                this.startAuthenticateTimer();
                return account;
            }));
    }

    logout() {
        // revoke app permissions to logout completely because FB.logout() doesn't remove FB cookie
        FB.api('/me/permissions', 'delete', null, () => FB.logout());
        this.stopAuthenticateTimer();
        this.accountSubject.next(null);
        this.router.navigate(['/login']);
    }

    getAll() {
        return this.http.get<Account[]>(baseUrl);
    }

    getById(id) {
        return this.http.get<Account>(`${baseUrl}/${id}`);
    }
    
    update(id, params) {
        return this.http.put(`${baseUrl}/${id}`, params)
            .pipe(map((account: any) => {
                // update the current account if it was updated
                if (account.id === this.accountValue.id) {
                    // publish updated account to subscribers
                    account = { ...this.accountValue, ...account };
                    this.accountSubject.next(account);
                }
                return account;
            }));
    }

    delete(id: string) {
        return this.http.delete(`${baseUrl}/${id}`)
            .pipe(finalize(() => {
                // auto logout if the logged in account was deleted
                if (id === this.accountValue.id)
                    this.logout();
            }));
    }

    // helper methods

    private authenticateTimeout;

    private startAuthenticateTimer() {
        // parse json object from base64 encoded jwt token
        const jwtToken = JSON.parse(atob(this.accountValue.token.split('.')[1]));

        // set a timeout to re-authenticate with the api one minute before the token expires
        const expires = new Date(jwtToken.exp * 1000);
        const timeout = expires.getTime() - Date.now() - (60 * 1000);
        const { accessToken } = FB.getAuthResponse();
        this.authenticateTimeout = setTimeout(() => {
            this.apiAuthenticate(accessToken).subscribe();
        }, timeout);
    }

    private stopAuthenticateTimer() {
        // cancel timer for re-authenticating with the api
        clearTimeout(this.authenticateTimeout);
    }
}

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.

 


Supported by