Browse Source

function: authentication with firebase

main
erdar2 4 years ago
parent
commit
304336a697
  1. 6
      src/app/app-routing.module.ts
  2. 30
      src/app/app.component.html
  3. 36
      src/app/app.component.less
  4. 76
      src/app/app.component.ts
  5. 18
      src/app/app.module.ts
  6. 24
      src/app/components/phone-book/phone-book.component.html
  7. 35
      src/app/components/phone-book/phone-book.component.less
  8. 25
      src/app/components/phone-book/phone-book.component.spec.ts
  9. 88
      src/app/components/phone-book/phone-book.component.ts
  10. 0
      src/app/components/record-dialog/record-dialog.component.html
  11. 0
      src/app/components/record-dialog/record-dialog.component.less
  12. 0
      src/app/components/record-dialog/record-dialog.component.spec.ts
  13. 0
      src/app/components/record-dialog/record-dialog.component.ts
  14. 0
      src/app/components/record/record.component.html
  15. 0
      src/app/components/record/record.component.less
  16. 0
      src/app/components/record/record.component.spec.ts
  17. 0
      src/app/components/record/record.component.ts
  18. 0
      src/app/components/record/record.ts
  19. 4
      src/app/components/sign-in/formData.ts
  20. 15
      src/app/components/sign-in/sign-in.component.html
  21. 9
      src/app/components/sign-in/sign-in.component.less
  22. 25
      src/app/components/sign-in/sign-in.component.spec.ts
  23. 30
      src/app/components/sign-in/sign-in.component.ts
  24. 16
      src/app/shared/guard/auth.guard.spec.ts
  25. 26
      src/app/shared/guard/auth.guard.ts
  26. 16
      src/app/shared/services/auth.service.spec.ts
  27. 69
      src/app/shared/services/auth.service.ts
  28. 7
      src/app/shared/services/user.ts

6
src/app/app-routing.module.ts

@ -1,8 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PhoneBookComponent } from './components/phone-book/phone-book.component';
import { SignInComponent } from './components/sign-in/sign-in.component';
import { AuthGuard } from './shared/guard/auth.guard';
const routes: Routes = [
{ path: '', component: PhoneBookComponent, canActivate: [AuthGuard] },
{ path: 'sign-in', component: SignInComponent },
];
@NgModule({

30
src/app/app.component.html

@ -1,30 +1,10 @@
<mat-toolbar color="primary">
<span>Phone Book</span>
<span class="spacer"></span>
<button *ngIf="authService.isLoggedIn" mat-icon-button aria-label="Logout" (click)="logout()">
<mat-icon>logout</mat-icon>
</button>
</mat-toolbar>
<div class="content-wrapper">
<button class="new-record" (click)="newRecord()" mat-button>
<mat-icon>add</mat-icon> Add Record
</button>
<mat-form-field appearance="fill">
<mat-icon matPrefix>search</mat-icon>
<mat-label>Search</mat-label>
<input matInput [value]="searchValue" (input)="search($event)" />
</mat-form-field>
<div class="container-wrapper">
<div class="container">
<mat-card>
<app-record
*ngFor="let record of filteredPhoneBook()"
[record]="record"
(edit)="editRecord($event)"
(delete)="deleteRecord($event)"
></app-record>
<p class="empty-label" *ngIf="filteredPhoneBook().length === 0">
No results
</p>
</mat-card>
</div>
</div>
</div>
<router-outlet></router-outlet>
</div>

36
src/app/app.component.less

@ -6,38 +6,6 @@ mat-toolbar {
margin-left: 10px;
}
.content-wrapper {
max-width: 1400px;
margin: auto;
}
.container-wrapper {
display: flex;
justify-content: space-around;
}
.container {
width: 100%;
margin: 0 25px 25px 0;
}
.list {
border: solid 1px #ccc;
min-height: 60px;
border-radius: 4px;
}
.new-record {
margin-bottom: 30px;
}
.empty-label {
font-size: 2em;
padding-top: 10px;
text-align: center;
opacity: 0.2;
}
.content-wrapper > *{
margin: 4px 4px 0px;
.spacer {
flex: 1 1 auto;
}

76
src/app/app.component.ts

@ -1,12 +1,5 @@
import { Component } from '@angular/core';
import { Record } from './record/record';
import { MatDialog } from '@angular/material/dialog';
import {
RecordDialogComponent,
RecordDialogResult,
} from './record-dialog/record-dialog.component';
import { AngularFirestore, Query } from '@angular/fire/compat/firestore';
import { Observable } from 'rxjs';
import { AuthService } from './shared/services/auth.service';
@Component({
selector: 'app-root',
@ -15,72 +8,11 @@ import { Observable } from 'rxjs';
})
export class AppComponent {
title = 'PhoneBook';
phoneBook = this.store
.collection('phoneBook')
.valueChanges({ idField: 'id' }) as Observable<Record[]>;
searchValue = '';
phoneBookLocal : Record[] = [];
phoneBookObserver = {
next: (x: Record[]) => {
this.phoneBookLocal = x;
},
error: (err: String) => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
constructor(public authService : AuthService){
constructor(private dialog: MatDialog, private store: AngularFirestore) {
this.phoneBook.subscribe(this.phoneBookObserver);
}
newRecord(): void {
const dialogRef = this.dialog.open(RecordDialogComponent, {
width: '270px',
data: {
record: {},
},
});
dialogRef
.afterClosed()
.subscribe((result: RecordDialogResult | undefined) => {
if (!result) {
return;
}
this.store.collection('phoneBook').add(result.record);
});
}
editRecord(record: Record): void {
const dialogRef = this.dialog.open(RecordDialogComponent, {
width: '270px',
data: {
record: {
name: record.name,
phoneNumber: record.phoneNumber,
},
},
});
dialogRef
.afterClosed()
.subscribe((result: RecordDialogResult | undefined) => {
if (!result) {
return;
}
this.store.collection('phoneBook').doc(record.id).update(result.record);
});
}
deleteRecord(record: Record): void {
this.store.collection('phoneBook').doc(record.id).delete();
}
search(event: Event): void {
this.searchValue = (event.target as HTMLInputElement).value;
}
filteredPhoneBook(): Record[]{
if(this.searchValue == "")
return this.phoneBookLocal;
return this.phoneBookLocal.filter(p => p.name.includes(this.searchValue) || p.phoneNumber.includes(this.searchValue));
logout(): void {
this.authService.logout();
}
}

18
src/app/app.module.ts

@ -6,24 +6,31 @@ import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatToolbarModule } from '@angular/material/toolbar';
import { RecordComponent } from './record/record.component';
import { RecordComponent } from './components/record/record.component';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { RecordDialogComponent } from './record-dialog/record-dialog.component';
import { RecordDialogComponent } from './components/record-dialog/record-dialog.component';
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
import { MatGridListModule } from '@angular/material/grid-list';
import { environment } from 'src/environments/environment';
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFirestoreModule } from '@angular/fire/compat/firestore';
import { AngularFireAuthModule } from "@angular/fire/compat/auth";
import { SignInComponent } from './components/sign-in/sign-in.component';
import { PhoneBookComponent } from './components/phone-book/phone-book.component';
import { AuthService } from './shared/services/auth.service';
@NgModule({
declarations: [
AppComponent,
RecordComponent,
RecordDialogComponent
RecordDialogComponent,
SignInComponent,
PhoneBookComponent
],
imports: [
BrowserModule,
@ -38,9 +45,10 @@ import { AngularFirestoreModule } from '@angular/fire/compat/firestore';
MatIconModule,
MatGridListModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule
AngularFirestoreModule,
AngularFireAuthModule
],
providers: [],
providers: [AuthService],
bootstrap: [AppComponent]
})
export class AppModule { }

24
src/app/components/phone-book/phone-book.component.html

@ -0,0 +1,24 @@
<button class="new-record" (click)="newRecord()" mat-button>
<mat-icon>add</mat-icon> Add Record
</button>
<mat-form-field appearance="fill">
<mat-icon matPrefix>search</mat-icon>
<mat-label>Search</mat-label>
<input matInput [value]="searchValue" (input)="search($event)" />
</mat-form-field>
<div class="container-wrapper">
<div class="container">
<mat-card>
<app-record
*ngFor="let record of filteredPhoneBook()"
[record]="record"
(edit)="editRecord($event)"
(delete)="deleteRecord($event)"
></app-record>
<p class="empty-label" *ngIf="filteredPhoneBook().length === 0">
No results
</p>
</mat-card>
</div>
</div>

35
src/app/components/phone-book/phone-book.component.less

@ -0,0 +1,35 @@
.content-wrapper {
max-width: 1400px;
margin: auto;
}
.container-wrapper {
display: flex;
justify-content: space-around;
}
.container {
width: 100%;
margin: 0 25px 25px 0;
}
.list {
border: solid 1px #ccc;
min-height: 60px;
border-radius: 4px;
}
.new-record {
margin-bottom: 30px;
}
.empty-label {
font-size: 2em;
padding-top: 10px;
text-align: center;
opacity: 0.2;
}
.content-wrapper > * {
margin: 4px 4px 0px;
}

25
src/app/components/phone-book/phone-book.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PhoneBookComponent } from './phone-book.component';
describe('PhoneBookComponent', () => {
let component: PhoneBookComponent;
let fixture: ComponentFixture<PhoneBookComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PhoneBookComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PhoneBookComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

88
src/app/components/phone-book/phone-book.component.ts

@ -0,0 +1,88 @@
import { Component } from '@angular/core';
import { Record } from '../record/record';
import { MatDialog } from '@angular/material/dialog';
import {
RecordDialogComponent,
RecordDialogResult,
} from '../record-dialog/record-dialog.component';
import { AngularFirestore } from '@angular/fire/compat/firestore';
import { Observable } from 'rxjs';
@Component({
selector: 'app-phone-book',
templateUrl: './phone-book.component.html',
styleUrls: ['./phone-book.component.less'],
})
export class PhoneBookComponent {
phoneBook = this.store
.collection('phoneBook')
.valueChanges({ idField: 'id' }) as Observable<Record[]>;
searchValue = '';
phoneBookLocal: Record[] = [];
phoneBookObserver = {
next: (x: Record[]) => {
this.phoneBookLocal = x;
},
error: (err: String) => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
constructor(private dialog: MatDialog, private store: AngularFirestore) {
this.phoneBook.subscribe(this.phoneBookObserver);
}
newRecord(): void {
const dialogRef = this.dialog.open(RecordDialogComponent, {
width: '270px',
data: {
record: {},
},
});
dialogRef
.afterClosed()
.subscribe((result: RecordDialogResult | undefined) => {
if (!result) {
return;
}
this.store.collection('phoneBook').add(result.record);
});
}
editRecord(record: Record): void {
const dialogRef = this.dialog.open(RecordDialogComponent, {
width: '270px',
data: {
record: {
name: record.name,
phoneNumber: record.phoneNumber,
},
},
});
dialogRef
.afterClosed()
.subscribe((result: RecordDialogResult | undefined) => {
if (!result) {
return;
}
this.store.collection('phoneBook').doc(record.id).update(result.record);
});
}
deleteRecord(record: Record): void {
this.store.collection('phoneBook').doc(record.id).delete();
}
search(event: Event): void {
this.searchValue = (event.target as HTMLInputElement).value;
}
filteredPhoneBook(): Record[] {
if (this.searchValue == '') return this.phoneBookLocal;
return this.phoneBookLocal.filter(
(p) =>
p.name.includes(this.searchValue) ||
p.phoneNumber.includes(this.searchValue)
);
}
}

0
src/app/record-dialog/record-dialog.component.html → src/app/components/record-dialog/record-dialog.component.html

0
src/app/record-dialog/record-dialog.component.less → src/app/components/record-dialog/record-dialog.component.less

0
src/app/record-dialog/record-dialog.component.spec.ts → src/app/components/record-dialog/record-dialog.component.spec.ts

0
src/app/record-dialog/record-dialog.component.ts → src/app/components/record-dialog/record-dialog.component.ts

0
src/app/record/record.component.html → src/app/components/record/record.component.html

0
src/app/record/record.component.less → src/app/components/record/record.component.less

0
src/app/record/record.component.spec.ts → src/app/components/record/record.component.spec.ts

0
src/app/record/record.component.ts → src/app/components/record/record.component.ts

0
src/app/record/record.ts → src/app/components/record/record.ts

4
src/app/components/sign-in/formData.ts

@ -0,0 +1,4 @@
export interface formData{
email: string;
password: string;
}

15
src/app/components/sign-in/sign-in.component.html

@ -0,0 +1,15 @@
<div class="container">
<mat-card>
<mat-form-field appearance="fill">
<mat-label>Email</mat-label>
<input matInput cdkFocusInitial [(ngModel)]="data.email" type="text" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Password</mat-label>
<input matInput [(ngModel)]="data.password" type="password" />
</mat-form-field>
<button mat-button (click)="login()">Sign in</button>
</mat-card>
</div>

9
src/app/components/sign-in/sign-in.component.less

@ -0,0 +1,9 @@
:host {
display: flex;
flex-direction: column;
align-items: center;
}
mat-card > *{
display: block;
}

25
src/app/components/sign-in/sign-in.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SignInComponent } from './sign-in.component';
describe('SignInComponent', () => {
let component: SignInComponent;
let fixture: ComponentFixture<SignInComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SignInComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SignInComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

30
src/app/components/sign-in/sign-in.component.ts

@ -0,0 +1,30 @@
import { Component, OnInit, Inject } from '@angular/core';
import { AuthService } from '../../shared/services/auth.service';
import { formData } from './formData';
@Component({
selector: 'app-sign-in',
templateUrl: './sign-in.component.html',
styleUrls: ['./sign-in.component.less']
})
export class SignInComponent implements OnInit {
data: formData = {
email: "",
password: ""
};
constructor(private authService: AuthService) {}
ngOnInit(): void {
}
login() {
this.authService.login(
this.data.email,
this.data.password
);
}
}

16
src/app/shared/guard/auth.guard.spec.ts

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthGuard } from './auth.guard';
describe('AuthGuard', () => {
let guard: AuthGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(AuthGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});

26
src/app/shared/guard/auth.guard.ts

@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
public authService: AuthService,
public router: Router
){ }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if(this.authService.isLoggedIn !== true) {
this.router.navigate(['sign-in'])
}
return true;
}
}

16
src/app/shared/services/auth.service.spec.ts

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

69
src/app/shared/services/auth.service.ts

@ -0,0 +1,69 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/compat/firestore';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import * as firebase from 'firebase/app';
import { User } from './user';
@Injectable()
export class AuthService {
userData: any;
constructor(
public afs: AngularFirestore, // Inject Firestore service
public afAuth: AngularFireAuth, // Inject Firebase auth service
public router: Router
) {
/* Saving user data in localstorage when
logged in and setting up null when logged out */
this.afAuth.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user') as string);
} else {
localStorage.setItem('user', null as any);
JSON.parse(localStorage.getItem('user') as string);
}
})
}
login(email : string, password : string) {
return this.afAuth.signInWithEmailAndPassword(email, password)
.then((result) => {
this.SetUserData(result.user as User);
localStorage.setItem('user', JSON.stringify(result.user));
JSON.parse(localStorage.getItem('user') as string);
this.router.navigate(['']);
}).catch((error) => {
window.alert(error.message)
})
}
get isLoggedIn(): boolean {
const user = JSON.parse(localStorage.getItem('user') as string);
return user !== null;
}
SetUserData(user: User) {
const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.uid}`);
const userData: User = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
emailVerified: user.emailVerified
}
return userRef.set(userData, {
merge: true
})
}
logout() {
return this.afAuth.signOut().then(() => {
localStorage.removeItem('user');
this.router.navigate(['sign-in']);
})
}
}

7
src/app/shared/services/user.ts

@ -0,0 +1,7 @@
export interface User {
uid: string;
email: string;
displayName: string;
photoURL: string;
emailVerified: boolean;
}
Loading…
Cancel
Save