I have an Angular2 app that allows login via Google or Facebook using Firebase (via AngularFire2). It also permits linking of accounts, so that a user can link his/her Google and Facebook logins to the same Firebase uid.
On my ng2 User Profile component, for currently authenticated users, I want to be able to display a button for linking a FB or Google account if it is not already linked. AngularFire2 exposes an observable of type FirebaseAuthState that includes an “auth” property of type firebase.User. The firebase.User object includes a providerData array that includes all of the providers (Facebook, Google, Github, etc) associated with the user. My AuthService looks something like this:
import * as firebase from 'firebase';
import { Injectable } from '@angular/core';
import { AuthProviders, AuthMethods,
FirebaseAuth, FirebaseAuthState } from 'angularfire2';
@Injectable()
export class AuthService {
authState: FirebaseAuthState = null;
constructor(private auth$: FirebaseAuth) {
auth$.subscribe((state) => {this.authState = state;})
}
linkTo(provider:string): firebase.Promise<firebase.auth.UserCredential> {
// provider param is a string (e.g., ‘facebook.com’);
// linkWithPopup() returns a firebase promise of type firebase.AuthState
// Not included here: code to convert provider param from string to
// firebase.AuthProvider
return this.authState.auth.linkWithPopup(provider);
}
unLinkFrom(provider:string): firebase.Promise<firebase.auth.UserCredential> {
// provider is a string (e.g. ‘facebook.com’)
// unlink() returns a firebase promise of type firebase.User.
return this.authState.auth.unlink (provider);
}
I can use this to determine which providers are already associate with a given user, and hide/show the requisite link buttons depending on whether a provider is contained within the user’s providerData array. In my component, for example:
export class UserProfileComponent implements OnInit {
private linkedProviders;
constructor() {} (private authService: AuthService) {
this. linkedProviders = authService.authState.auth.providerData;
}
}
When I link a provider, firebase returns a firebase.Promise object. On successful linking, I use .then() to update the local linkedProviders array, which contains the providerData array from Firebase. For example:
// Link a provider to the current account
linkProvider(provider:string) {
this.authService.linkTo(provider)
.then((state) => this.linkedProviders = state.user.providerData)
.catch(err => console.log(err));
}
// Unlink a provider from the current account
unLinkProvider(provider:string) {
this.authService.unLinkAccount(provider)
.then(user => this.linkedProviders = user.providerData)
.catch(err => console.log(err));
}
However, the this.linkedProviders array doesn’t seem to update automatically in the template using *ngFor when a user links or unlinks a provider, despite the fact that the .then() block of the linkProvider/unLinkProvider modifies it to reflect the updated data returned from the firebase Promise.
Actually, it DOES seem to modify the local array data, as logging this.linkedProviders to the console will show. However, unless I click another button in the template, the change is not applied to the *ngFor. I can't figure why this is happening or how to work around it. I'm sure I'm missing something, but I can't figure out what.
It might be cleaner make authState an observable so the component can subscribe to it. The component can then call linkWithPopup or unlink on the authState in the subscription. At the moment it looks like linkTo and unlinkFrom methods can be called before authState has been set.