@@ -9,6 +9,10 @@
|
|
9 |
<a routerLink="/books" routerLinkActive="active"
|
10 |
ariaCurrentWhenActive="page">Books</a>
|
11 |
</li>
|
|
|
|
|
|
|
|
|
12 |
</ul>
|
13 |
</nav>
|
14 |
<router-outlet />
|
|
|
9 |
<a routerLink="/books" routerLinkActive="active"
|
10 |
ariaCurrentWhenActive="page">Books</a>
|
11 |
</li>
|
12 |
+
<li>
|
13 |
+
<a routerLink="/admin" routerLinkActive="active"
|
14 |
+
ariaCurrentWhenActive="page">Admin</a>
|
15 |
+
</li>
|
16 |
</ul>
|
17 |
</nav>
|
18 |
<router-outlet />
|
@@ -2,9 +2,11 @@ import { Routes } from '@angular/router';
|
|
2 |
|
3 |
import { HomePage } from './home-page/home-page';
|
4 |
import { booksPortalRoutes } from './books-portal/books-portal.routes';
|
|
|
5 |
|
6 |
export const routes: Routes = [
|
7 |
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
8 |
{ path: 'home', component: HomePage, title: 'BookMonkey' },
|
9 |
-
...booksPortalRoutes
|
|
|
10 |
];
|
|
|
2 |
|
3 |
import { HomePage } from './home-page/home-page';
|
4 |
import { booksPortalRoutes } from './books-portal/books-portal.routes';
|
5 |
+
import { booksAdminRoutes } from './books-admin/books-admin.routes';
|
6 |
|
7 |
export const routes: Routes = [
|
8 |
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
9 |
{ path: 'home', component: HomePage, title: 'BookMonkey' },
|
10 |
+
...booksPortalRoutes,
|
11 |
+
...booksAdminRoutes
|
12 |
];
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
<h1>Create book</h1>
|
2 |
+
<app-book-form (submitBook)="create($event)" />
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Component, inject } from '@angular/core';
|
2 |
+
import { Router } from '@angular/router';
|
3 |
+
|
4 |
+
import { BookForm } from '../book-form/book-form';
|
5 |
+
import { BookStore } from '../../shared/book-store';
|
6 |
+
import { Book } from '../../shared/book';
|
7 |
+
|
8 |
+
@Component({
|
9 |
+
selector: 'app-book-create',
|
10 |
+
imports: [BookForm],
|
11 |
+
templateUrl: './book-create.ng.html',
|
12 |
+
styleUrl: './book-create.scss'
|
13 |
+
})
|
14 |
+
export class BookCreate {
|
15 |
+
#store = inject(BookStore);
|
16 |
+
#router = inject(Router);
|
17 |
+
|
18 |
+
create(book: Book) {
|
19 |
+
this.#store.createBook(book).subscribe(createdBook => {
|
20 |
+
this.#router.navigate(['/books', 'details', createdBook.isbn]);
|
21 |
+
});
|
22 |
+
}
|
23 |
+
}
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@let c = bookForm.controls;
|
2 |
+
|
3 |
+
<form [formGroup]="bookForm" (ngSubmit)="submitForm()">
|
4 |
+
<label for="title">Title</label>
|
5 |
+
<input id="title" [formControl]="c.title" />
|
6 |
+
|
7 |
+
<label for="subtitle">Subtitle</label>
|
8 |
+
<input id="subtitle" [formControl]="c.subtitle" />
|
9 |
+
|
10 |
+
<label for="isbn">ISBN</label>
|
11 |
+
<input id="isbn" [formControl]="c.isbn" />
|
12 |
+
|
13 |
+
<fieldset>
|
14 |
+
<legend>Authors</legend>
|
15 |
+
<button type="button" (click)="addAuthorControl()">Add Author</button>
|
16 |
+
<div role="group">
|
17 |
+
@for (authorControl of c.authors.controls; track $index) {
|
18 |
+
<input
|
19 |
+
[attr.aria-label]="'Author ' + $index"
|
20 |
+
[formControl]="authorControl"
|
21 |
+
/>
|
22 |
+
}
|
23 |
+
</div>
|
24 |
+
</fieldset>
|
25 |
+
|
26 |
+
<label for="description">Description</label>
|
27 |
+
<textarea id="description" [formControl]="c.description"></textarea>
|
28 |
+
|
29 |
+
<label for="imageUrl">Thumbnail URL</label>
|
30 |
+
<input type="url" id="imageUrl" [formControl]="c.imageUrl" />
|
31 |
+
|
32 |
+
<button type="submit">Save</button>
|
33 |
+
</form>
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Component, output } from '@angular/core';
|
2 |
+
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
3 |
+
|
4 |
+
import { Book } from '../../shared/book';
|
5 |
+
|
6 |
+
@Component({
|
7 |
+
selector: 'app-book-form',
|
8 |
+
imports: [ReactiveFormsModule],
|
9 |
+
templateUrl: './book-form.ng.html',
|
10 |
+
styleUrl: './book-form.scss'
|
11 |
+
})
|
12 |
+
export class BookForm {
|
13 |
+
readonly submitBook = output<Book>();
|
14 |
+
|
15 |
+
bookForm = new FormGroup({
|
16 |
+
isbn: new FormControl('', { nonNullable: true }),
|
17 |
+
title: new FormControl('', { nonNullable: true }),
|
18 |
+
subtitle: new FormControl('', { nonNullable: true }),
|
19 |
+
description: new FormControl('', { nonNullable: true }),
|
20 |
+
authors: new FormArray([
|
21 |
+
new FormControl('', { nonNullable: true })
|
22 |
+
]),
|
23 |
+
imageUrl: new FormControl('', { nonNullable: true })
|
24 |
+
});
|
25 |
+
|
26 |
+
addAuthorControl() {
|
27 |
+
this.bookForm.controls.authors.push(
|
28 |
+
new FormControl('', { nonNullable: true })
|
29 |
+
);
|
30 |
+
}
|
31 |
+
|
32 |
+
submitForm() {
|
33 |
+
const formValue = this.bookForm.getRawValue();
|
34 |
+
const authors = formValue.authors.filter(author => !!author);
|
35 |
+
|
36 |
+
const newBook: Book = {
|
37 |
+
...formValue,
|
38 |
+
authors,
|
39 |
+
createdAt: new Date().toISOString()
|
40 |
+
};
|
41 |
+
|
42 |
+
this.submitBook.emit(newBook);
|
43 |
+
}
|
44 |
+
}
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Routes } from '@angular/router';
|
2 |
+
|
3 |
+
import { BookCreate } from './book-create/book-create';
|
4 |
+
|
5 |
+
export const booksAdminRoutes: Routes = [
|
6 |
+
{ path: 'admin', redirectTo: 'admin/create' },
|
7 |
+
{ path: 'admin/create', component: BookCreate, title: 'Create Book' }
|
8 |
+
];
|
@@ -27,4 +27,8 @@ export class BookStore {
|
|
27 |
deleteBook(isbn: string): Observable<unknown> {
|
28 |
return this.#http.delete(`${this.#apiUrl}/books/${isbn}`);
|
29 |
}
|
|
|
|
|
|
|
|
|
30 |
}
|
|
|
27 |
deleteBook(isbn: string): Observable<unknown> {
|
28 |
return this.#http.delete(`${this.#apiUrl}/books/${isbn}`);
|
29 |
}
|
30 |
+
|
31 |
+
createBook(book: Book): Observable<Book> {
|
32 |
+
return this.#http.post<Book>(`${this.#apiUrl}/books`, book);
|
33 |
+
}
|
34 |
}
|