20230731-angular-Erwieterung-authantificationAnd
This commit is contained in:
14
src/app/admin/admin-routing.module.ts
Normal file
14
src/app/admin/admin-routing.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { BookCreateComponent } from './book-create/book-create.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'admin', redirectTo: 'admin/create' },
|
||||
{ path: 'admin/create', component: BookCreateComponent },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AdminRoutingModule {}
|
||||
16
src/app/admin/admin.module.ts
Normal file
16
src/app/admin/admin.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AdminRoutingModule } from './admin-routing.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BookFormComponent } from './book-form/book-form.component';
|
||||
import { BookCreateComponent } from './book-create/book-create.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
BookFormComponent,
|
||||
BookCreateComponent
|
||||
],
|
||||
imports: [CommonModule, AdminRoutingModule, FormsModule],
|
||||
})
|
||||
export class AdminModule {}
|
||||
0
src/app/admin/book-create/book-create.component.css
Normal file
0
src/app/admin/book-create/book-create.component.css
Normal file
3
src/app/admin/book-create/book-create.component.html
Normal file
3
src/app/admin/book-create/book-create.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>Create Book</h1>
|
||||
|
||||
<bm-book-form (submitBook)="create($event)"></bm-book-form>
|
||||
21
src/app/admin/book-create/book-create.component.spec.ts
Normal file
21
src/app/admin/book-create/book-create.component.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BookCreateComponent } from './book-create.component';
|
||||
|
||||
describe('BookCreateComponent', () => {
|
||||
let component: BookCreateComponent;
|
||||
let fixture: ComponentFixture<BookCreateComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BookCreateComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(BookCreateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
22
src/app/admin/book-create/book-create.component.ts
Normal file
22
src/app/admin/book-create/book-create.component.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { BookStoreService } from 'src/app/shared/book-store.service';
|
||||
import { Book } from 'src/shared/book';
|
||||
|
||||
@Component({
|
||||
selector: 'bm-book-create',
|
||||
templateUrl: './book-create.component.html',
|
||||
styleUrls: ['./book-create.component.css'],
|
||||
})
|
||||
export class BookCreateComponent {
|
||||
constructor(
|
||||
private service: BookStoreService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
create(book: Book) {
|
||||
this.service.create(book).subscribe((createdBook) => {
|
||||
this.router.navigate(['/books', createdBook.isbn]);
|
||||
});
|
||||
}
|
||||
}
|
||||
0
src/app/admin/book-form/book-form.component.css
Normal file
0
src/app/admin/book-form/book-form.component.css
Normal file
19
src/app/admin/book-form/book-form.component.html
Normal file
19
src/app/admin/book-form/book-form.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<form (ngSubmit)="submitForm()" #form="ngForm">
|
||||
<label for="title">Title</label>
|
||||
<input name="title" id="title" [(ngModel)]="book.title" required />
|
||||
|
||||
<label for="isbn">ISBN</label>
|
||||
<input
|
||||
name="isbn"
|
||||
id="isbn"
|
||||
[(ngModel)]="book.isbn"
|
||||
required
|
||||
minlength="10"
|
||||
maxlength="13"
|
||||
/>
|
||||
|
||||
<label for="author">Author</label>
|
||||
<input name="author" id="author" [(ngModel)]="book.authors[0]" required />
|
||||
|
||||
<button type="submit" [disabled]="form.invalid">Save</button>
|
||||
</form>
|
||||
21
src/app/admin/book-form/book-form.component.spec.ts
Normal file
21
src/app/admin/book-form/book-form.component.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BookFormComponent } from './book-form.component';
|
||||
|
||||
describe('BookFormComponent', () => {
|
||||
let component: BookFormComponent;
|
||||
let fixture: ComponentFixture<BookFormComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BookFormComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(BookFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
21
src/app/admin/book-form/book-form.component.ts
Normal file
21
src/app/admin/book-form/book-form.component.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
import { Book } from 'src/shared/book';
|
||||
|
||||
@Component({
|
||||
selector: 'bm-book-form',
|
||||
templateUrl: './book-form.component.html',
|
||||
styleUrls: ['./book-form.component.css'],
|
||||
})
|
||||
export class BookFormComponent {
|
||||
@Output() submitBook = new EventEmitter<Book>();
|
||||
|
||||
submitForm() {
|
||||
this.submitBook.emit(this.book);
|
||||
}
|
||||
|
||||
book: Book = {
|
||||
isbn: '',
|
||||
title: '',
|
||||
authors: [''],
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,21 @@
|
||||
<nav>
|
||||
<a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page"
|
||||
>Home</a
|
||||
>
|
||||
<a routerLink="/books" routerLinkActive="active" ariaCurrentWhenActive="page"
|
||||
>Books</a
|
||||
>
|
||||
<a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page">
|
||||
Home
|
||||
</a>
|
||||
<a routerLink="/books" routerLinkActive="active" ariaCurrentWhenActive="page">
|
||||
Books
|
||||
</a>
|
||||
<a routerLink="/admin" routerLinkActive="active" ariaCurrentWhenActive="page">
|
||||
Administrator
|
||||
</a>
|
||||
<div class="actions">
|
||||
<button class="green" (click)="auth.login()" *ngIf="!auth.isAuthenticated">
|
||||
Login
|
||||
</button>
|
||||
<button class="red" (click)="auth.logout()" *ngIf="auth.isAuthenticated">
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Book } from 'src/shared/book';
|
||||
|
||||
import { AuthService } from './shared/auth.service';
|
||||
@Component({
|
||||
selector: 'bm-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'book-monkey';
|
||||
book: Book | null = null;
|
||||
constructor(public auth: AuthService) {}
|
||||
|
||||
showList() {
|
||||
this.book = null;
|
||||
}
|
||||
showDetails(book: Book) {
|
||||
this.book = book;
|
||||
}
|
||||
title = 'book-monkey';
|
||||
}
|
||||
|
||||
@@ -5,12 +5,17 @@ import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BooksModule } from './books/books.module';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||
import { SearchComponent } from './search/search.component';
|
||||
import { AuthInterceptor } from './shared/auth.interceptor';
|
||||
import { AdminModule } from './admin/admin.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, HomeComponent],
|
||||
imports: [BrowserModule, AppRoutingModule, BooksModule, HttpClientModule],
|
||||
providers: [],
|
||||
declarations: [AppComponent, HomeComponent, SearchComponent],
|
||||
imports: [BrowserModule, AppRoutingModule, BooksModule, HttpClientModule, AdminModule],
|
||||
providers: [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -15,8 +15,4 @@ export class BookListComponent {
|
||||
constructor(private service: BookStoreService) {
|
||||
this.books$ = this.service.getAll();
|
||||
}
|
||||
|
||||
doSelect(book: Book) {
|
||||
this.selectBook.emit(book);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<h1>Home</h1>
|
||||
|
||||
<a routerLink="/books" class="button red">Show book list</a>
|
||||
|
||||
<h2>Search</h2>
|
||||
<bm-search></bm-search>
|
||||
|
||||
0
src/app/search/search.component.css
Normal file
0
src/app/search/search.component.css
Normal file
18
src/app/search/search.component.html
Normal file
18
src/app/search/search.component.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<input
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
aria-label="Search"
|
||||
[class.loading]="isLoading"
|
||||
#searchInput
|
||||
(input)="input$.next(searchInput.value)"
|
||||
/>
|
||||
|
||||
<ul class="search-results" *ngIF="results$ | async as results">
|
||||
<li *ngFor="let book of results">
|
||||
<a [routerLink]="['/books', book.isbn]">
|
||||
{{ book.title }}
|
||||
<p role="doc-subtitle">{{ book.subtitle }}></p>
|
||||
</a>
|
||||
</li>
|
||||
<li *ngIf="!results.length">No results</li>
|
||||
</ul>
|
||||
21
src/app/search/search.component.spec.ts
Normal file
21
src/app/search/search.component.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SearchComponent } from './search.component';
|
||||
|
||||
describe('SearchComponent', () => {
|
||||
let component: SearchComponent;
|
||||
let fixture: ComponentFixture<SearchComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SearchComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(SearchComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
34
src/app/search/search.component.ts
Normal file
34
src/app/search/search.component.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
Subject,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
switchMap,
|
||||
tap,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import { BookStoreService } from '../shared/book-store.service';
|
||||
import { Book } from 'src/shared/book';
|
||||
|
||||
@Component({
|
||||
selector: 'bm-search',
|
||||
templateUrl: './search.component.html',
|
||||
styleUrls: ['./search.component.css'],
|
||||
})
|
||||
export class SearchComponent {
|
||||
input$ = new Subject<string>();
|
||||
isLoading = false;
|
||||
results$: Observable<Book[]>;
|
||||
|
||||
constructor(private service: BookStoreService) {
|
||||
this.results$ = this.input$.pipe(
|
||||
filter((term) => term.length >= 3),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap(() => (this.isLoading = true)),
|
||||
switchMap((term) => this.service.getAllSearch(term)),
|
||||
tap(() => (this.isLoading = false)),
|
||||
);
|
||||
}
|
||||
}
|
||||
16
src/app/shared/auth.interceptor.spec.ts
Normal file
16
src/app/shared/auth.interceptor.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthInterceptor } from './auth.interceptor';
|
||||
|
||||
describe('AuthInterceptor', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [
|
||||
AuthInterceptor
|
||||
]
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor);
|
||||
expect(interceptor).toBeTruthy();
|
||||
});
|
||||
});
|
||||
30
src/app/shared/auth.interceptor.ts
Normal file
30
src/app/shared/auth.interceptor.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpHandler,
|
||||
HttpEvent,
|
||||
HttpInterceptor,
|
||||
} from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
constructor(private authService: AuthService) {}
|
||||
intercept(
|
||||
request: HttpRequest<unknown>,
|
||||
next: HttpHandler,
|
||||
): Observable<HttpEvent<unknown>> {
|
||||
const token = '1234567890';
|
||||
if (this.authService.isAuthenticated) {
|
||||
//token in hearder einfügen
|
||||
const reqWithToken = request.clone({
|
||||
setHeaders: { Authorization: `Bearer${token}` },
|
||||
});
|
||||
return next.handle(reqWithToken);
|
||||
} else {
|
||||
//Request unverändert weitergeben
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/app/shared/auth.service.spec.ts
Normal file
16
src/app/shared/auth.service.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
24
src/app/shared/auth.service.ts
Normal file
24
src/app/shared/auth.service.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
private _isAuthenticated$ = new BehaviorSubject(true);
|
||||
readonly isAuthenticated$ = this._isAuthenticated$.asObservable();
|
||||
|
||||
constructor() {}
|
||||
|
||||
get isAuthenticated() {
|
||||
return this._isAuthenticated$.value;
|
||||
}
|
||||
|
||||
login() {
|
||||
this._isAuthenticated$.next(true);
|
||||
}
|
||||
|
||||
logout() {
|
||||
this._isAuthenticated$.next(false);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, catchError, of } from 'rxjs';
|
||||
import { Book } from 'src/shared/book';
|
||||
|
||||
@Injectable({
|
||||
@@ -12,7 +12,12 @@ export class BookStoreService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getAll(): Observable<Book[]> {
|
||||
return this.http.get<Book[]>(`${this.apiUrl}/books`);
|
||||
return this.http.get<Book[]>(`${this.apiUrl}/books`).pipe(
|
||||
catchError((err) => {
|
||||
console.error(err);
|
||||
return of([]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
getSingle(isbn: string): Observable<Book> {
|
||||
@@ -22,4 +27,17 @@ export class BookStoreService {
|
||||
remove(isbn: string): Observable<unknown> {
|
||||
return this.http.delete(`${this.apiUrl}/books/${isbn}`);
|
||||
}
|
||||
|
||||
getAllSearch(term: string): Observable<Book[]> {
|
||||
return this.http.get<Book[]>(`${this.apiUrl}/books/search/${term}`).pipe(
|
||||
catchError((err) => {
|
||||
console.error(err);
|
||||
return of([]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
create(book: Book): Observable<Book> {
|
||||
return this.http.post<Book>(`${this.apiUrl}/books`, book);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user