|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { NgModule } from '@angular/core';
|
| 2 |
import { RouterModule, Routes } from '@angular/router';
|
| 3 |
import { BookCreateComponent } from './book-create/book-create.component';
|
|
|
|
| 4 |
|
| 5 |
const routes: Routes = [
|
| 6 |
{
|
|
@@ -10,6 +11,10 @@ const routes: Routes = [
|
|
| 10 |
{
|
| 11 |
path: 'admin/create',
|
| 12 |
component: BookCreateComponent,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
];
|
| 15 |
|
| 1 |
import { NgModule } from '@angular/core';
|
| 2 |
import { RouterModule, Routes } from '@angular/router';
|
| 3 |
import { BookCreateComponent } from './book-create/book-create.component';
|
| 4 |
+
import { BookEditComponent } from './book-edit/book-edit.component';
|
| 5 |
|
| 6 |
const routes: Routes = [
|
| 7 |
{
|
| 11 |
{
|
| 12 |
path: 'admin/create',
|
| 13 |
component: BookCreateComponent,
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
path: 'admin/edit/:isbn',
|
| 17 |
+
component: BookEditComponent,
|
| 18 |
}
|
| 19 |
];
|
| 20 |
|
|
@@ -1,20 +1,24 @@
|
|
| 1 |
import { NgModule } from '@angular/core';
|
| 2 |
import { CommonModule } from '@angular/common';
|
| 3 |
-
import {
|
|
|
|
| 4 |
|
| 5 |
import { AdminRoutingModule } from './admin-routing.module';
|
| 6 |
import { BookFormComponent } from './book-form/book-form.component';
|
| 7 |
import { BookCreateComponent } from './book-create/book-create.component';
|
|
|
|
| 8 |
|
| 9 |
@NgModule({
|
| 10 |
declarations: [
|
| 11 |
BookFormComponent,
|
| 12 |
-
BookCreateComponent
|
|
|
|
| 13 |
],
|
| 14 |
imports: [
|
| 15 |
CommonModule,
|
| 16 |
AdminRoutingModule,
|
| 17 |
-
|
|
|
|
| 18 |
],
|
| 19 |
})
|
| 20 |
export class AdminModule { }
|
| 1 |
import { NgModule } from '@angular/core';
|
| 2 |
import { CommonModule } from '@angular/common';
|
| 3 |
+
import { ReactiveFormsModule } from '@angular/forms';
|
| 4 |
+
import { LocalIsoDateValueAccessor } from 'angular-date-value-accessor';
|
| 5 |
|
| 6 |
import { AdminRoutingModule } from './admin-routing.module';
|
| 7 |
import { BookFormComponent } from './book-form/book-form.component';
|
| 8 |
import { BookCreateComponent } from './book-create/book-create.component';
|
| 9 |
+
import { BookEditComponent } from './book-edit/book-edit.component';
|
| 10 |
|
| 11 |
@NgModule({
|
| 12 |
declarations: [
|
| 13 |
BookFormComponent,
|
| 14 |
+
BookCreateComponent,
|
| 15 |
+
BookEditComponent
|
| 16 |
],
|
| 17 |
imports: [
|
| 18 |
CommonModule,
|
| 19 |
AdminRoutingModule,
|
| 20 |
+
ReactiveFormsModule,
|
| 21 |
+
LocalIsoDateValueAccessor
|
| 22 |
],
|
| 23 |
})
|
| 24 |
export class AdminModule { }
|
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h1>Edit Book</h1>
|
| 2 |
+
|
| 3 |
+
<bm-book-form
|
| 4 |
+
*ngIf="book$ | async as book"
|
| 5 |
+
[book]="book"
|
| 6 |
+
(submitBook)="update($event)"></bm-book-form>
|
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component } from '@angular/core';
|
| 2 |
+
import { ActivatedRoute, Router } from '@angular/router';
|
| 3 |
+
import { map, Observable, switchMap } from 'rxjs';
|
| 4 |
+
|
| 5 |
+
import { Book } from '../../shared/book';
|
| 6 |
+
import { BookStoreService } from '../../shared/book-store.service';
|
| 7 |
+
|
| 8 |
+
@Component({
|
| 9 |
+
selector: 'bm-book-edit',
|
| 10 |
+
templateUrl: './book-edit.component.html',
|
| 11 |
+
standalone: false,
|
| 12 |
+
styleUrl: './book-edit.component.css'
|
| 13 |
+
})
|
| 14 |
+
export class BookEditComponent {
|
| 15 |
+
book$: Observable<Book>;
|
| 16 |
+
|
| 17 |
+
constructor(
|
| 18 |
+
private service: BookStoreService,
|
| 19 |
+
private route: ActivatedRoute,
|
| 20 |
+
private router: Router
|
| 21 |
+
) {
|
| 22 |
+
this.book$ = this.route.paramMap.pipe(
|
| 23 |
+
map(params => params.get('isbn')!),
|
| 24 |
+
switchMap(isbn => this.service.getSingle(isbn))
|
| 25 |
+
);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
update(book: Book) {
|
| 29 |
+
this.service.update(book).subscribe(updatedBook => {
|
| 30 |
+
this.router.navigate(['/books', updatedBook.isbn]);
|
| 31 |
+
});
|
| 32 |
+
}
|
| 33 |
+
}
|
|
@@ -1,26 +1,33 @@
|
|
| 1 |
-
<form (ngSubmit)="submitForm()"
|
| 2 |
<label for="title">Title</label>
|
| 3 |
-
<input
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
required>
|
| 8 |
|
| 9 |
<label for="isbn">ISBN</label>
|
| 10 |
-
<input
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
<label for="
|
| 19 |
-
<input
|
| 20 |
-
name="author"
|
| 21 |
-
id="author"
|
| 22 |
-
[(ngModel)]="book.authors[0]"
|
| 23 |
-
required>
|
| 24 |
|
| 25 |
<button type="submit" [disabled]="form.invalid">
|
| 26 |
Save
|
| 1 |
+
<form [formGroup]="form" (ngSubmit)="submitForm()">
|
| 2 |
<label for="title">Title</label>
|
| 3 |
+
<input id="title" formControlName="title">
|
| 4 |
+
|
| 5 |
+
<label for="subtitle">Subtitle</label>
|
| 6 |
+
<input id="subtitle" formControlName="subtitle">
|
|
|
|
| 7 |
|
| 8 |
<label for="isbn">ISBN</label>
|
| 9 |
+
<input id="isbn" formControlName="isbn">
|
| 10 |
+
|
| 11 |
+
<label>Authors</label>
|
| 12 |
+
<button type="button" class="add"
|
| 13 |
+
(click)="addAuthorControl()">
|
| 14 |
+
+ Author
|
| 15 |
+
</button>
|
| 16 |
+
<fieldset formArrayName="authors">
|
| 17 |
+
<input
|
| 18 |
+
*ngFor="let a of authors.controls; index as i"
|
| 19 |
+
[attr.aria-label]="'Author ' + i"
|
| 20 |
+
[formControlName]="i">
|
| 21 |
+
</fieldset>
|
| 22 |
+
|
| 23 |
+
<label for="description">Description</label>
|
| 24 |
+
<textarea id="description" formControlName="description"></textarea>
|
| 25 |
+
|
| 26 |
+
<label for="published">Published</label>
|
| 27 |
+
<input type="date" useValueAsLocalIso id="published" formControlName="published">
|
| 28 |
|
| 29 |
+
<label for="thumbnailUrl">Thumbnail URL</label>
|
| 30 |
+
<input type="url" id="thumbnailUrl" formControlName="thumbnailUrl">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
<button type="submit" [disabled]="form.invalid">
|
| 33 |
Save
|
|
@@ -1,4 +1,5 @@
|
|
| 1 |
-
import { Component, Output, EventEmitter } from '@angular/core';
|
|
|
|
| 2 |
|
| 3 |
import { Book } from '../../shared/book';
|
| 4 |
|
|
@@ -8,16 +9,81 @@ import { Book } from '../../shared/book';
|
|
| 8 |
standalone: false,
|
| 9 |
styleUrl: './book-form.component.css'
|
| 10 |
})
|
| 11 |
-
export class BookFormComponent {
|
| 12 |
-
book
|
| 13 |
-
isbn: '',
|
| 14 |
-
title: '',
|
| 15 |
-
authors: ['']
|
| 16 |
-
};
|
| 17 |
-
|
| 18 |
@Output() submitBook = new EventEmitter<Book>();
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
submitForm() {
|
| 21 |
-
this.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
}
|
| 23 |
}
|
| 1 |
+
import { Component, Output, EventEmitter, Input, OnChanges } from '@angular/core';
|
| 2 |
+
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
|
| 3 |
|
| 4 |
import { Book } from '../../shared/book';
|
| 5 |
|
| 9 |
standalone: false,
|
| 10 |
styleUrl: './book-form.component.css'
|
| 11 |
})
|
| 12 |
+
export class BookFormComponent implements OnChanges {
|
| 13 |
+
@Input() book?: Book;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
@Output() submitBook = new EventEmitter<Book>();
|
| 15 |
|
| 16 |
+
form = new FormGroup({
|
| 17 |
+
title: new FormControl('', {
|
| 18 |
+
nonNullable: true,
|
| 19 |
+
validators: Validators.required,
|
| 20 |
+
}),
|
| 21 |
+
subtitle: new FormControl('', { nonNullable: true }),
|
| 22 |
+
isbn: new FormControl('', {
|
| 23 |
+
nonNullable: true,
|
| 24 |
+
validators: [
|
| 25 |
+
Validators.required,
|
| 26 |
+
Validators.minLength(10),
|
| 27 |
+
Validators.maxLength(13),
|
| 28 |
+
]
|
| 29 |
+
}),
|
| 30 |
+
description: new FormControl('', { nonNullable: true }),
|
| 31 |
+
published: new FormControl('', { nonNullable: true }),
|
| 32 |
+
authors: this.buildAuthorsArray(['']),
|
| 33 |
+
thumbnailUrl: new FormControl('', { nonNullable: true })
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
ngOnChanges(): void {
|
| 37 |
+
if (this.book) {
|
| 38 |
+
this.setFormValues(this.book);
|
| 39 |
+
this.setEditMode(true);
|
| 40 |
+
} else {
|
| 41 |
+
this.setEditMode(false);
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
private setFormValues(book: Book) {
|
| 46 |
+
this.form.patchValue(book);
|
| 47 |
+
|
| 48 |
+
this.form.setControl(
|
| 49 |
+
'authors',
|
| 50 |
+
this.buildAuthorsArray(book.authors)
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
private setEditMode(isEditing: boolean) {
|
| 55 |
+
const isbnControl = this.form.controls.isbn;
|
| 56 |
+
|
| 57 |
+
if (isEditing) {
|
| 58 |
+
isbnControl.disable();
|
| 59 |
+
} else {
|
| 60 |
+
isbnControl.enable();
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
private buildAuthorsArray(authors: string[]) {
|
| 65 |
+
return new FormArray(
|
| 66 |
+
authors.map(v => new FormControl(v, { nonNullable: true }))
|
| 67 |
+
);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
get authors() {
|
| 71 |
+
return this.form.controls.authors;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
addAuthorControl() {
|
| 75 |
+
this.authors.push(new FormControl('', { nonNullable: true }));
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
submitForm() {
|
| 79 |
+
const formValue = this.form.getRawValue();
|
| 80 |
+
const authors = formValue.authors.filter(author => !!author);
|
| 81 |
+
|
| 82 |
+
const newBook: Book = {
|
| 83 |
+
...formValue,
|
| 84 |
+
authors
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
this.submitBook.emit(newBook);
|
| 88 |
}
|
| 89 |
}
|
|
@@ -26,4 +26,8 @@
|
|
| 26 |
<button class="red" (click)="removeBook(book.isbn)">
|
| 27 |
Remove book
|
| 28 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
</div>
|
| 26 |
<button class="red" (click)="removeBook(book.isbn)">
|
| 27 |
Remove book
|
| 28 |
</button>
|
| 29 |
+
<a class="button"
|
| 30 |
+
[routerLink]="['/admin/edit', book.isbn]">
|
| 31 |
+
Edit book
|
| 32 |
+
</a>
|
| 33 |
</div>
|
|
@@ -41,4 +41,11 @@ export class BookStoreService {
|
|
| 41 |
create(book: Book): Observable<Book> {
|
| 42 |
return this.http.post<Book>(`${this.apiUrl}/books`, book);
|
| 43 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 41 |
create(book: Book): Observable<Book> {
|
| 42 |
return this.http.post<Book>(`${this.apiUrl}/books`, book);
|
| 43 |
}
|
| 44 |
+
|
| 45 |
+
update(book: Book): Observable<Book> {
|
| 46 |
+
return this.http.put<Book>(
|
| 47 |
+
`${this.apiUrl}/books/${book.isbn}`,
|
| 48 |
+
book
|
| 49 |
+
);
|
| 50 |
+
}
|
| 51 |
}
|