Update all user related data when user logs in (observable) - javascript

I am working on an angular-application with authentication. The application has a dashboard which shows the username of the logged in user. The problem is, that when you login with a new user, it still shows the username of the last logged in user.
So it doesen't update the username (observable) when a user logs in.
I think the problem is that I get the username in the ngOnInit methode.
How can I update all user related data?
header.component.ts (username should be shown)
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../../core/auth.service';
import { Apollo } from 'apollo-angular';
import gpl from 'graphql-tag';
const registeredUser = gpl`
query registeredUser {
registeredUser {
name
}
}
`
#Component({
selector: 'dashboard-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
private user$;
constructor(
private authService : AuthService,
private apollo : Apollo) { }
ngOnInit() {
this.user$ = this.apollo.watchQuery<any>({
query: registeredUser
})
.valueChanges;
}
logout() {
this.authService.logout();
}
}
AuthService
import { Injectable } from '#angular/core';
import { JwtHelper } from 'angular2-jwt';
import { Apollo } from 'apollo-angular';
import gpl from 'graphql-tag';
import { Router } from '#angular/router';
const register = gpl`
mutation($name: String!, $email_mobile: String!, $password: String!) {
register(name: $name, email_mobile: $email_mobile, password: $password) {
success
token
user {
name
email
mobile
}
errors {
message
key
}
}
}
`;
const login = gpl`
mutation($email_mobile: String!, $password: String!) {
login(email_mobile: $email_mobile, password: $password) {
success
token
user {
name
email
mobile
}
errors {
message
key
}
}
}
`;
#Injectable()
export class AuthService {
constructor(
private jwtHelper: JwtHelper,
private apollo: Apollo,
private router: Router) { }
isLoggedIn(): boolean {
const token = localStorage.getItem('token');
if(!token)
return false;
// Check whether the token is expired and return
// true or false
return !this.jwtHelper.isTokenExpired(token);
}
async register(name: string, email_mobile: string, password: string) {
let regInfo = {};
await this.apollo.mutate({
mutation: register,
variables: {
name,
email_mobile,
password
}
}).subscribe(({data}) => {
const regData = data.register;
if(regData.success) {
// set token to Local Storage
localStorage.setItem("token", regData.token);
this.router.navigate(['/dashboard']);
} else {
regInfo["errors"] = regData.errors;
}
regInfo["success"] = regData.success;
});
return regInfo;
}
async login(email_mobile: string, password: string) {
let regInfo = {};
await this.apollo.mutate({
mutation: login,
variables: {
email_mobile,
password
}
}).subscribe(({data}) => {
const regData = data.login;
if(regData.success) {
// set token to Local Storage
localStorage.setItem("token", regData.token);
regInfo["user"] = regData.user;
this.router.navigate(['/dashboard']);
} else {
regInfo["errors"] = regData.errors;
}
regInfo["success"] = regData.success;
});
return regInfo;
}
logout(){
localStorage.removeItem("token");
}
}

You should subscribe to changes in your Apollo class, not just take them one off. Your code retrieves the variable, but does not stay to listen in on the pipe, when another user flies by.
Your code
this.user$ = this.apollo.watchQuery<any>({
query: registeredUser
})
.valueChanges;
What I envision
this.apollo.valueChanges.subscribe(
({ data }) => {
this.user$ = [...data.user];
}
);
And remember kids, please unsusbcribe from your pipes!
Read more about GraphQL Subscriptions here!
https://alligator.io/angular/graphql-subscriptions/

Related

Getting users data from firebase database doesn't work after authentication

I am new in angular and firebase and trying to get users data to table only after succesful auth - if you log in correctly, the table shows the data, and if not, you can't see this data. I've tried to make simple firebase login and logout in my AuthService:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { AngularFireAuth } from '#angular/fire/auth';
import { Router } from '#angular/router';
import * as firebase from 'firebase/app';
#Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private router: Router, private fireAuth: AngularFireAuth) {}
onLogin(email: string, password: string) {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(function () {
console.log('Succes');
})
.catch(function (error) {
console.log(error);
});
}
async onLogout() {
try {
await firebase.auth().signOut();
this.router.navigate(['./']);
} catch (error) {
console.log(error);
}
}
}
And this is my UserService responsible for displaying users in the table:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { User } from 'src/app/models/user.model';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root',
})
export class UserService {
constructor(private http: HttpClient) {}
fetchUsers() {
let result = new Subject<User[]>();
this.http
.get('https://fir-login-1416c.firebaseio.com/users.json')
.subscribe((users) => {
let usersAr = Object.keys(users).map((id) => new User(users[id]));
result.next(usersAr);
});
return result.pipe(take(1));
}
addUser(user: User) {
let postData: User = user;
this.http
.post<{ name: string }>(
'https://fir-login-1416c.firebaseio.com/users.json',
postData,
{
observe: 'response',
}
)
.subscribe(
(responseData) => {
console.log(responseData.body.name);
},
(error) => {
console.log(error);
}
);
this.fetchUsers();
}
deleteUser() {
// Later
}
}
My firebase database rules looks like this:
{
"rules": {
".write": "auth !== null",
".read": "auth !== null"
}
}
But probelm is when I log in with the correct data and navigate to page with users table I see empty table and console shows
this errors.
It looks like the authentication didn't work at all or I just did something wrong.
If you have any suggestions on how to do this, give them to me :) Thanks!
You need to send some kind of authorisation headers with your request. Otherwise the database doesn't really know that your are authenticated.
I would suggest to use AngularFire not only for authentication but also for fetching data.

Firebase Auth : Error: A network error (such as timeout, interrupted connection or unreachable host) has occurred

I'm working on authentication the project works fine and connect to the database in the firebase, because the project had parts when I need to retrieve data from it, when I try to login with right email and password I get the current error:
Error: A network error (such as timeout, interrupted connection or unreachable host) has occurred.
the loginAdminService:
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { AngularFireAuth } from "#angular/fire/auth";
import * as fireBase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class LoginAdminserviceService {
isAuth: boolean;
constructor(private angularFireAuth: AngularFireAuth,loginDatabase: AngularFireDatabase) {
}
async login(email: string, password: string) {
return new Promise(
(resolve,reject)=>{
fireBase.auth().signInWithEmailAndPassword(email,password).then(
()=>{
this.isAuth = true;
resolve();
},
(error)=>{
this.isAuth = false;
reject(error);
}
)
}
)
}
async logout() {
return await this.angularFireAuth.auth.signOut();
}
isUserLoggedIn() {
return JSON.parse(localStorage.getItem('user'));
}
}
the authentication component:
import { Component, OnInit } from '#angular/core';
import { LoginAdminserviceService } from '../services/login-adminservice.service';
import { Route, Router } from '#angular/router';
#Component({
selector: 'app-authentication',
templateUrl: './authentication.component.html',
styleUrls: ['./authentication.component.css']
})
export class AuthenticationComponent implements OnInit {
constructor(private route:Router , public loginServiceasAdmin : LoginAdminserviceService) { }
ngOnInit() {
}
async loginAdmin(email:string,password:string){
this.loginServiceasAdmin.login(email,password).then(
()=>{
alert('Bienvenue '+email);
this.route.navigate(['/listreclamation']);
},
(error)=>{
console.log('Pas de connexion '+error);
alert('Votre compte est incorrect');
});
}
}
the html page:
<form>
Email:<input type="text" #email><br>
Password:<input type="password" #password><br>
<button type="submit" (click)="loginAdmin(email.value,password.value)">Login as Admin</button>
<button type="submit" (click)="this.loginServiceasAdmin.logout()">Logout</button>
</form>
Simple example how login service should looks like:
export class AuthService {
user$: Observable<firebase.User>
constructor(private afAuth: AngularFireAuth) {
this.user$ = this.syncUser()
}
// function trigered once are listen when user is logged in.
syncUser() {
return this.afAuth.authState.pipe(
switchMap(user => {
if(user){
return of(user)
} else {
return of(null)
}
})
)
}
// return is not necesery you can allways listen in real time user$ variable.
async signInWith(credentials: IdCredentials) {
const result = await this.afAuth.auth.signInWithEmailAndPassword(credentials.email, credentials.password)
return result
}
}
Make sure you that in your module fire your use emulators Url is as follows:
['http://localhost:<portnumber>', <portnumber>]
and not
['http://localhost', <portnumber>]

Angular: how to make localStorage works async

I trying to get data when I log in, by sending ID from localStorage. Everything I tried didn't work, and the only thing comes to my mind is that getting ID from local storage works synchronously. I hope someone can help me make it async. Unfortunately, I don't have permission to show API here. The code:
auth.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '#angular/common/http';
import { throwError, Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Restaurant } from '../models/Restaurant';
import { LocalStorage } from '#ngx-pwa/local-storage';
#Injectable({
providedIn: 'root'
})
export class AuthService {
loginUrl = 'xxxxxxxxxx';
errorData: {};
constructor(private http: HttpClient) { }
redirectUrl: string;
login(email: string, password: string) {
var postData = {email: email, password: password};
return this.http.post<Restaurant>(this.loginUrl, postData)
.pipe(map(restaurant => {
if (restaurant) {
localStorage.setItem('currentRestaurant', JSON.stringify(restaurant));
return restaurant;
}
}),
catchError(this.handleError)
);
}
isLoggedIn() {
if (localStorage.getItem('currentRestaurant')) {
return true;
}
return false;
}
getAuthorizationToken() {
const currentRestaurant = JSON.parse(localStorage.getItem('currentRestaurant'));
return currentRestaurant.token;
}
logout() {
localStorage.removeItem('currentRestaurant');
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong.
console.error(`Backend returned code ${error.status}, ` + `body was: ${error.error}`);
}
// return an observable with a user-facing error message
this.errorData = {
errorTitle: 'Oops! Request for document failed',
errorDesc: 'Something bad happened. Please try again later.'
};
return throwError(this.errorData);
}
currRestaurant: Restaurant = JSON.parse(localStorage.getItem('currentRestaurant'));
currID = this. currRestaurant.id;
}
login.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, Validators, FormGroup } from '#angular/forms';
import { Router } from '#angular/router';
import { AuthService } from '../services/auth.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
submitted = false;
returnUrl: string;
error: {};
loginError: string;
constructor(
private fb: FormBuilder,
private router: Router,
private authService: AuthService
) { }
ngOnInit() {
this.loginForm = this.fb.group({
email: ['', Validators.required],
password: ['', Validators.required]
});
this.authService.logout();
}
get email() { return this.loginForm.get('email'); }
get password() { return this.loginForm.get('password'); }
onSubmit() {
this.submitted = true;
this.authService.login( this.email.value, this.password.value).subscribe((data) => {
if (this.authService.isLoggedIn) {
const redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/';
this.router.navigate([redirect]);
} else {
this.loginError = 'email or password is incorrect.';
}
},
error => this.error = error
);
}
}
Thanks everyone for their time
There are some mistakes:
Are you aware that you use Native localStorage, not the one you import - import { LocalStorage } from '#ngx-pwa/local-storage'; (and also it should be injected in the constructor if you want to use it, and used in asynchronous way)
if (this.authService.isLoggedIn) { will always be true, because this.authService.isLoggedIn is a function and it is not a falsy value. You probably want to execute it - if (this.authService.isLoggedIn()) {
redirectUrl is always undefined because your provided snippets does not assign it any value.

Restricting List of Items by User Account ID

Using Angular 4, Ngrx Store & AngularFire2
I am having real problems understanding how I can restrict a list of items from Firebase based on the currently logged in user account id.
I am using ngrx as well including ngrx effects.
The steps I need to follow are:
• Get Current Users UID – Auth Object
• Get User Object based on the UID from step above
• Get Company List based on the User Account ID Above
My problem is that because I am calling firebase as an Observable the call to company list is being made before I complete the first two steps.
The code is as per below, if someone can assist that would be appreciated:
The problem is in the getEntityList Method on the generic firebase service - I have marked where the problem is
Company Component
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { Store } from '#ngrx/store';
import { AppState } from './../../../core/models/index';
import { CompanyModel } from './../../../core/models/index';
import { getCompanies} from './../../../core/store/actions/company.actions';
#Component({
selector: 'mj-company',
templateUrl: './company.component.html',
styleUrls: ['./company.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CompanyComponent implements OnInit {
entityList$: Observable<CompanyModel[]>;
constructor(private store: Store<AppState>) {
this.entityList$ = this.store.select(state => state.companies);
}
ngOnInit() { this.store.dispatch(getCompanies()); }
}
Company Actions
import { CompanyModel } from './../../models';
import { Action } from '#ngrx/store';
export const ActionTypes = {
GET_COMPANIES: 'GET_COMPANIES',
GET_COMPANIES_SUCCESS: 'GET_COMPANIES_SUCCESS',
GET_COMPANIES_ERROR: 'GET_COMPANIES_ERROR'
};
export function getCompanies() {
return {
type: ActionTypes.GET_COMPANIES,
entityRef: 'companys'
}
}
}
Company Reducer
import { ActionReducer, Action } from '#ngrx/store';
import { ActionTypes } from '../actions/company.actions';
import { CompanyModel } from '../../models';
export function companyReducer(state = [<CompanyModel>{}], action: Action) {
switch (action.type) {
case ActionTypes.GET_COMPANIES:
return action.payload;
case ActionTypes.GET_COMPANIES_SUCCESS:
return action.payload;
case ActionTypes.GET_COMPANIES_ERROR:
return action.payload;
default:
return state;
}
};
Company Effect
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { ActionTypes } from '../actions/company.actions';
import { Actions, Effect } from '#ngrx/effects';
import { FirebaseDataService } from './../../services/firebase-data.service';
#Injectable()
export class CompanyEffects {
constructor(
private actions$: Actions,
private firebaseDataService: FirebaseDataService
) { }
// tslint:disable-next-line:member-ordering
#Effect() getCompanies$ = this.actions$
.ofType(ActionTypes.GET_COMPANIES)
.switchMap(action =>
this.firebaseDataService.getEntityList(action.entityRef)
.map(companies => ({ type: ActionTypes.GET_COMPANIES_SUCCESS, payload: companies }))
.catch(() => Observable.of({ type: ActionTypes.GET_COMPANIES_ERROR })));
Firebase Generic Data Service
import { Injectable } from '#angular/core';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable } from 'angularfire2/database';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { FirebaseUtilityService } from './../../core/services/firebase-utility.service';
import { UserModel, CompanyModel } from './../models/index';
#Injectable()
export class FirebaseDataService {
$key: string;
loginId: string;
currentUser: any;
constructor(private db: AngularFireDatabase,
private authService: AuthService,
private firebaseService: FirebaseUtilityService) { }
// Return an observable list with optional query
getEntityList(firebaseRef: string, query = {}): FirebaseListObservable<any[]> {
this.loginId = this.authService.currentUserId;
// I get this instantly which is good
console.log('logId: ', this.loginId);
this.currentUser = this.db.object('users/' + this.loginId);
console.log('accountId: ', this.currentUser.accountId);
// This is where the problem is because at this stage the subscription above is not complete so accountId is undefined.
return this.db.list(firebaseRef, {
query: {
orderByChild: 'accountId',
equalTo: this.currentUser.accountId
}
});
// return this.db.list(firebaseRef, query);
}
// Return a single observable item
getEntity(firebaseRef: string, key: string): FirebaseObjectObservable<any> {
const itemPath = `${firebaseRef}/${key}`;
return this.db.object(itemPath)
}
// Default error handling for all actions
private handleError(error) {
console.log(error)
}
}
Problem solved - using switchMap
return this.currentUser.switchMap(user => {
return this.db.list(firebaseRef, {
query: {
orderByChild: 'accountId',
equalTo: user.accountId
}
});
})

Redirect to login page, if not logged in with AngularFire 2

Whats the best way to redirect the user to the login page, if he's not authenticated with Angular2 / AngularFire2?
For example; I want to protect the /dashboard page from not authenticated users. The user should instantly get redirected to the /login page,
I'm using
angular2 version 2.0.0-rc.1
angular2/router version 2.0.0-rc.1
firebase version 3.0.5
angularfire2 version 2.0.0-beta.1
You can protect urls using Angular 2's UI router guard conditions.
https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard
An example using firebase
Guard Component
Note it would probably be better to replace referencing AF here, with an authentication service.
import { Injectable } from '#angular/core';
import { CanActivate, Router } from '#angular/router';
import { AngularFire } from 'angularfire2';
#Injectable()
export class CanActivateViaAuthGuard implements CanActivate {
user;
constructor(private af: AngularFire, private router: Router) {
this.af.auth.subscribe(user => {
if (user) {
this.user = user;
} else {
this.user = undefined;
}
});
}
canActivate() {
if (this.user) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
Application Routes
import { CanActivateViaAuthGuard} from './CanActivateViaAuthGuard';
const routes: Routes = [
{path: '/Login', component: LoginComponent},
{path: '/dashboard', component: DashboardComponent, canActivate: [CanActivateViaAuthGuard]}
];
export const routing = RouterModule.forRoot(routes);
Finally the login code
onSubmit() {
this.af.auth.login({
email: this.email,
password: this.password,
}).then(() => {
this.submitted = true;
this.router.navigate(['/dashboard', this.dashboard]);
});
}
You can use the Auth method here.
if ($scope.auth === null) {
$state.go('login');
}
Inject your $firebaseAuth and assign it to the $scope.auth then let the if check of its true or false
Found the solution.
Thanks to todo-angular-2 from r-park
import { ReflectiveInjector } from '#angular/core';
import { Router } from '#angular/router-deprecated';
import { AuthService } from './auth-service';
let appInjector: ReflectiveInjector;
/**
* This is a workaround until `CanActivate` supports DI
* #see https://github.com/angular/angular/issues/4112
*/
export class AuthRouteHelper {
static dependencies(): {auth: AuthService, router: Router} {
const injector: ReflectiveInjector = AuthRouteHelper.injector();
const auth: AuthService = injector.get(AuthService);
const router: Router = injector.get(Router);
return {auth, router};
}
static injector(injector?: ReflectiveInjector): ReflectiveInjector {
if (injector) appInjector = injector;
return appInjector;
}
static requireAuth(): boolean {
const { auth, router } = AuthRouteHelper.dependencies();
if (!auth.authenticated) router.navigate(['/SignIn']);
return auth.authenticated;
}
static requireUnauth(): boolean {
const { auth, router } = AuthRouteHelper.dependencies();
if (auth.authenticated) router.navigate(['/Tasks']);
return !auth.authenticated;
}
}

Categories

Resources