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>
|
<nav>
|
||||||
<a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page"
|
<a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page">
|
||||||
>Home</a
|
Home
|
||||||
>
|
</a>
|
||||||
<a routerLink="/books" routerLinkActive="active" ariaCurrentWhenActive="page"
|
<a routerLink="/books" routerLinkActive="active" ariaCurrentWhenActive="page">
|
||||||
>Books</a
|
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>
|
</nav>
|
||||||
<main>
|
<main>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Book } from 'src/shared/book';
|
import { AuthService } from './shared/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'bm-root',
|
selector: 'bm-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css'],
|
styleUrls: ['./app.component.css'],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'book-monkey';
|
constructor(public auth: AuthService) {}
|
||||||
book: Book | null = null;
|
|
||||||
|
|
||||||
showList() {
|
title = 'book-monkey';
|
||||||
this.book = null;
|
|
||||||
}
|
|
||||||
showDetails(book: Book) {
|
|
||||||
this.book = book;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ import { AppRoutingModule } from './app-routing.module';
|
|||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { BooksModule } from './books/books.module';
|
import { BooksModule } from './books/books.module';
|
||||||
import { HomeComponent } from './home/home.component';
|
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({
|
@NgModule({
|
||||||
declarations: [AppComponent, HomeComponent],
|
declarations: [AppComponent, HomeComponent, SearchComponent],
|
||||||
imports: [BrowserModule, AppRoutingModule, BooksModule, HttpClientModule],
|
imports: [BrowserModule, AppRoutingModule, BooksModule, HttpClientModule, AdminModule],
|
||||||
providers: [],
|
providers: [
|
||||||
|
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||||
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -15,8 +15,4 @@ export class BookListComponent {
|
|||||||
constructor(private service: BookStoreService) {
|
constructor(private service: BookStoreService) {
|
||||||
this.books$ = this.service.getAll();
|
this.books$ = this.service.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
doSelect(book: Book) {
|
|
||||||
this.selectBook.emit(book);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
|
|
||||||
<a routerLink="/books" class="button red">Show book list</a>
|
<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 { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, catchError, of } from 'rxjs';
|
||||||
import { Book } from 'src/shared/book';
|
import { Book } from 'src/shared/book';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -12,7 +12,12 @@ export class BookStoreService {
|
|||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
getAll(): Observable<Book[]> {
|
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> {
|
getSingle(isbn: string): Observable<Book> {
|
||||||
@@ -22,4 +27,17 @@ export class BookStoreService {
|
|||||||
remove(isbn: string): Observable<unknown> {
|
remove(isbn: string): Observable<unknown> {
|
||||||
return this.http.delete(`${this.apiUrl}/books/${isbn}`);
|
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