Initial implementation of login and registration with fake backend for future testing
Also introduced flex-layout
This commit is contained in:
16
src/app/account/account.service.spec.ts
Normal file
16
src/app/account/account.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AccountService } from './account.service';
|
||||
|
||||
describe('AccountService', () => {
|
||||
let service: AccountService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AccountService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
38
src/app/account/account.service.ts
Normal file
38
src/app/account/account.service.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
import { User } from './user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AccountService {
|
||||
private userSubject: BehaviorSubject<User>;
|
||||
public user: Observable<User>;
|
||||
|
||||
constructor(private httpClient: HttpClient) {
|
||||
this.userSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('user')));
|
||||
this.user = this.userSubject.asObservable();
|
||||
}
|
||||
|
||||
public get userValue() {
|
||||
return this.userSubject.value;
|
||||
}
|
||||
|
||||
login(username, password) {
|
||||
return this.httpClient.post<User>(environment.apiUrl + '/fake_login', { username, password })
|
||||
.pipe((map(user => {
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
this.userSubject.next(user);
|
||||
return user;
|
||||
})))
|
||||
}
|
||||
|
||||
register(user) {
|
||||
return this.httpClient.post<User>(environment.apiUrl + '/fake_registration', user);
|
||||
}
|
||||
|
||||
}
|
||||
7
src/app/account/auth.guard.spec.ts
Normal file
7
src/app/account/auth.guard.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AuthGuard } from './auth.guard';
|
||||
|
||||
describe('Auth.Guard', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new AuthGuard()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
21
src/app/account/auth.guard.ts
Normal file
21
src/app/account/auth.guard.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { AccountService } from './account.service';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private router: Router,
|
||||
private accountService: AccountService) { }
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||
const user = this.accountService.userValue;
|
||||
|
||||
if (user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigate(['/login'], {queryParams: {returnURL: state.url}});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
22
src/app/account/login/login.component.css
Normal file
22
src/app/account/login/login.component.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.login-card {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 10px 40px;
|
||||
}
|
||||
|
||||
.mat-button {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
padding-top: 15px;
|
||||
}
|
||||
30
src/app/account/login/login.component.html
Normal file
30
src/app/account/login/login.component.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="login-container" fxLayout="row" fxLayoutAlign="center center">
|
||||
<mat-card class="login-card" fxLayout="column" fxLayoutAlign="center center">
|
||||
<mat-card-title>Login</mat-card-title>
|
||||
<mat-card-content>
|
||||
<form class="login-form" [formGroup]="form" (ngSubmit)="onLogin()">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>User name</mat-label>
|
||||
<label>
|
||||
<input matInput formControlName="username" required>
|
||||
</label>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Password</mat-label>
|
||||
<label>
|
||||
<input matInput formControlName="password" type="password" required minlength="15">
|
||||
</label>
|
||||
</mat-form-field>
|
||||
|
||||
<div fxLayout="row" fxLayoutAlign="space-evenly center">
|
||||
<button mat-flat-button color="primary" [disabled]="loading">
|
||||
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
Login
|
||||
</button>
|
||||
<a mat-button routerLink="/register">Sign up</a>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
25
src/app/account/login/login.component.spec.ts
Normal file
25
src/app/account/login/login.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
42
src/app/account/login/login.component.ts
Normal file
42
src/app/account/login/login.component.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { AccountService } from '../account.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.css']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
form: FormGroup = new FormGroup({
|
||||
username: new FormControl(''),
|
||||
password: new FormControl(''),
|
||||
});
|
||||
loading = false;
|
||||
returnUrl = this.activatedRoute.snapshot.queryParams['returnUrl'] || '/';
|
||||
|
||||
onLogin() {
|
||||
this.loading = true;
|
||||
this.accountService.login(this.form.controls['username'], this.form.controls['password'])
|
||||
.pipe(first())
|
||||
.subscribe(data => {
|
||||
this.router.navigate([this.returnUrl]);
|
||||
},
|
||||
error => {
|
||||
// TODO error handling
|
||||
console.log(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private accountService: AccountService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { PasswordErrorStateMatcher } from './password-error-state-matcher';
|
||||
|
||||
describe('PasswordErrorStateMatcher', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new PasswordErrorStateMatcher()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
11
src/app/account/register/password-error-state-matcher.ts
Normal file
11
src/app/account/register/password-error-state-matcher.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ErrorStateMatcher } from '@angular/material/core';
|
||||
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms';
|
||||
|
||||
export class PasswordErrorStateMatcher implements ErrorStateMatcher {
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
const invalidControl = !!(control && control.invalid && control.parent.touched);
|
||||
const invalidParent = !!(control && control.parent && control.parent.invalid && control.parent.touched);
|
||||
|
||||
return invalidControl || invalidParent;
|
||||
}
|
||||
}
|
||||
22
src/app/account/register/register.component.css
Normal file
22
src/app/account/register/register.component.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.register-card {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.register-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
padding: 10px 40px;
|
||||
}
|
||||
|
||||
.mat-button {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
padding-top: 15px;
|
||||
}
|
||||
48
src/app/account/register/register.component.html
Normal file
48
src/app/account/register/register.component.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<div class="register-container" fxLayout="row" fxLayoutAlign="center center">
|
||||
<mat-card class="register-card" fxLayout="column" fxLayoutAlign="center center">
|
||||
<mat-card-title>Sign up</mat-card-title>
|
||||
<mat-card-content>
|
||||
<form class="register-form" [formGroup]="form" (ngSubmit)="onRegister()">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>User name</mat-label>
|
||||
<label>
|
||||
<input matInput formControlName="username" required>
|
||||
</label>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Email</mat-label>
|
||||
<label>
|
||||
<input matInput formControlName="email" required email>
|
||||
</label>
|
||||
</mat-form-field>
|
||||
|
||||
<div formGroupName="password">
|
||||
<mat-form-field appearance="fill" hintLabel="At least 15 characters.">
|
||||
<mat-label>Password</mat-label>
|
||||
<label>
|
||||
<input matInput formControlName="first" type="password" required minlength="15">
|
||||
</label>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Confirm password</mat-label>
|
||||
<label>
|
||||
<input matInput formControlName="second" type="password" required minlength="15"
|
||||
[errorStateMatcher]="passwordErrorStateMatcher">
|
||||
</label>
|
||||
<mat-error *ngIf="form.get('password').hasError('mismatch')">Passwords don't match.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div fxLayout="row" fxLayoutAlign="space-evenly center">
|
||||
<button mat-flat-button color="primary" [disabled]="loading">
|
||||
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
Sign up
|
||||
</button>
|
||||
<a mat-button routerLink="/login">Login</a>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
25
src/app/account/register/register.component.spec.ts
Normal file
25
src/app/account/register/register.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RegisterComponent } from './register.component';
|
||||
|
||||
describe('RegisterComponent', () => {
|
||||
let component: RegisterComponent;
|
||||
let fixture: ComponentFixture<RegisterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ RegisterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RegisterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
65
src/app/account/register/register.component.ts
Normal file
65
src/app/account/register/register.component.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { AccountService } from '../account.service';
|
||||
import { PasswordErrorStateMatcher } from './password-error-state-matcher';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.css']
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
form = this.formBuilder.group({
|
||||
username: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: this.formBuilder.group({
|
||||
first: ['', [Validators.required, Validators.minLength(15)]],
|
||||
second: ['', [Validators.required, Validators.minLength(15)]],
|
||||
},
|
||||
{ validators: this.passwordsEqual }),
|
||||
});
|
||||
loading = false;
|
||||
passwordErrorStateMatcher = new PasswordErrorStateMatcher();
|
||||
|
||||
constructor(private accountService: AccountService,
|
||||
private formBuilder: FormBuilder,
|
||||
private router: Router,
|
||||
) { }
|
||||
|
||||
passwordsEqual(passwords: FormGroup): ValidationErrors | null {
|
||||
const password1 = passwords.get('first');
|
||||
const password2 = passwords.get('second');
|
||||
|
||||
return password1 && password2 && password1.value === password2.value ? null : { mismatch: true };
|
||||
}
|
||||
|
||||
readForm() {
|
||||
return {
|
||||
username: this.form.get('username').value,
|
||||
email: this.form.get('email').value,
|
||||
password: this.form.get('password').get('first').value,
|
||||
}
|
||||
}
|
||||
|
||||
onRegister() {
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.accountService.register(this.readForm())
|
||||
.pipe(first())
|
||||
.subscribe(data => {
|
||||
this.router.navigate(['/login']);
|
||||
},
|
||||
error => {
|
||||
// TODO error handling
|
||||
console.log(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void { }
|
||||
}
|
||||
7
src/app/account/user.spec.ts
Normal file
7
src/app/account/user.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { User } from './user';
|
||||
|
||||
describe('User', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new User()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
6
src/app/account/user.ts
Normal file
6
src/app/account/user.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class User {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
token: string;
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AuthGuard } from './account/auth.guard';
|
||||
import { LoginComponent } from './account/login/login.component';
|
||||
import { RegisterComponent } from './account/register/register.component';
|
||||
|
||||
|
||||
const routes: Routes = [];
|
||||
const routes: Routes = [
|
||||
{ path: '', component: AppComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
{ path: '**', redirectTo: '' },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
|
||||
@@ -16,69 +16,6 @@ svg.material-icons:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.card svg.material-icons path {
|
||||
fill: #888;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
/* margin-top: 16px; */
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eee;
|
||||
background-color: #fafafa;
|
||||
height: 40px;
|
||||
width: 200px;
|
||||
margin: 0 8px 16px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.card-container .card:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.card.card-small {
|
||||
height: 16px;
|
||||
width: 168px;
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card):hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card):hover .material-icons path {
|
||||
fill: rgb(105, 103, 103);
|
||||
}
|
||||
|
||||
.card.highlight-card {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
width: auto;
|
||||
min-width: 30%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card.card.highlight-card span {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media screen and (max-width: 767px) {
|
||||
|
||||
@@ -93,6 +30,10 @@ svg.material-icons:not(:last-child) {
|
||||
|
||||
}
|
||||
|
||||
.router {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat {
|
||||
float: right;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
<div class="chat">
|
||||
<app-chat></app-chat>
|
||||
<div class="router">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card-container">
|
||||
<div class="card card-small" (click)="onClickSocket()" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Send test message</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="onClickApi()" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Call rest api</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { SocketService } from './socket/socket.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { AccountService } from './account/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -10,22 +10,14 @@ import { HttpClient } from '@angular/common/http';
|
||||
export class AppComponent {
|
||||
title = 'rona-frontend';
|
||||
|
||||
onClickSocket() {
|
||||
this.socketService.send('test', {'user': 'USERNAME', 'payload': 'PAYLOAD TEST'})
|
||||
}
|
||||
|
||||
onClickApi() {
|
||||
this.httpService.get('http://localhost:5005/').subscribe(response => {
|
||||
console.log('REST API call returned: ', response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param accountService
|
||||
* @param socketService
|
||||
* @param httpService
|
||||
*/
|
||||
constructor(private socketService: SocketService,
|
||||
private httpService: HttpClient) { }
|
||||
constructor(private accountService: AccountService,
|
||||
private socketService: SocketService,
|
||||
) { }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ChatComponent } from './chat/chat.component';
|
||||
import { EntryComponent } from './chat/entry/entry.component';
|
||||
import { InputComponent } from './chat/input/input.component';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import { LoginComponent } from './account/login/login.component';
|
||||
import { TestComponent } from './test/test.component';
|
||||
import { RegisterComponent } from './account/register/register.component';
|
||||
import { fakeBackendProvider } from './utils/fake-backend';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ChatComponent,
|
||||
EntryComponent,
|
||||
InputComponent
|
||||
InputComponent,
|
||||
LoginComponent,
|
||||
TestComponent,
|
||||
RegisterComponent
|
||||
],
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
FlexLayoutModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
AppRoutingModule,
|
||||
],
|
||||
providers: [
|
||||
fakeBackendProvider,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule,
|
||||
BrowserAnimationsModule,
|
||||
MatCardModule,
|
||||
MatInputModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
0
src/app/test/test.component.css
Normal file
0
src/app/test/test.component.css
Normal file
15
src/app/test/test.component.html
Normal file
15
src/app/test/test.component.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div>
|
||||
<div class="card-container">
|
||||
<div class="card card-small" (click)="onClickSocket()" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Send test message</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="onClickApi()" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Call rest api</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
25
src/app/test/test.component.spec.ts
Normal file
25
src/app/test/test.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TestComponent } from './test.component';
|
||||
|
||||
describe('TestComponent', () => {
|
||||
let component: TestComponent;
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TestComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
29
src/app/test/test.component.ts
Normal file
29
src/app/test/test.component.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import {SocketService} from '../socket/socket.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-test',
|
||||
templateUrl: './test.component.html',
|
||||
styleUrls: ['./test.component.css']
|
||||
})
|
||||
export class TestComponent implements OnInit {
|
||||
|
||||
onClickSocket() {
|
||||
this.socketService.send('test', {'user': 'USERNAME', 'payload': 'PAYLOAD TEST'})
|
||||
}
|
||||
|
||||
onClickApi() {
|
||||
this.httpService.get('http://localhost:5005/').subscribe(response => {
|
||||
console.log('REST API call returned: ', response);
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private httpService: HttpClient,
|
||||
private socketService: SocketService,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
7
src/app/utils/fake-backend.spec.ts
Normal file
7
src/app/utils/fake-backend.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FakeBackend } from './fake-backend';
|
||||
|
||||
describe('FakeBackend', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new FakeBackend()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
73
src/app/utils/fake-backend.ts
Normal file
73
src/app/utils/fake-backend.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
|
||||
|
||||
|
||||
let users = JSON.parse(localStorage.getItem('users')) || [];
|
||||
|
||||
@Injectable()
|
||||
export class FakeBackend implements HttpInterceptor {
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const { url, method, headers, body } = request;
|
||||
|
||||
return of(null)
|
||||
.pipe(mergeMap(handleRoute))
|
||||
.pipe(materialize())
|
||||
.pipe(delay(500))
|
||||
.pipe(dematerialize());
|
||||
|
||||
function handleRoute() {
|
||||
switch (true) {
|
||||
case url.endsWith('fake_login') && method === 'POST':
|
||||
return authenticate();
|
||||
case url.endsWith('fake_registration') && method === 'POST':
|
||||
return register();
|
||||
default:
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
|
||||
function authenticate() {
|
||||
const { username, password } = body;
|
||||
const user = users.find(x => x.username === username.value && x.password === password.value);
|
||||
if (!user) {
|
||||
console.log(password);
|
||||
return error('Username or password is incorrect.');
|
||||
}
|
||||
return ok({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
token: 'fake-jwt-token',
|
||||
});
|
||||
}
|
||||
|
||||
function register() {
|
||||
const user = body;
|
||||
|
||||
if (users.find(x => x.username === user.username)) {
|
||||
return error('Username ' + user.username + ' is already taken.');
|
||||
}
|
||||
|
||||
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
|
||||
users.push(user);
|
||||
localStorage.setItem('users', JSON.stringify(users));
|
||||
console.log('Register user: ' + user);
|
||||
return ok();
|
||||
}
|
||||
|
||||
function ok(body?) {
|
||||
return of(new HttpResponse({ status: 200, body }));
|
||||
}
|
||||
|
||||
function error(message) {
|
||||
return throwError({ error: { message } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fakeBackendProvider = {
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: FakeBackend,
|
||||
multi: true,
|
||||
}
|
||||
Reference in New Issue
Block a user