Skip to content

Commit

Permalink
solve--add-a-book-new-form-and-route
Browse files Browse the repository at this point in the history
  • Loading branch information
martinakraus authored and Konstantin Pentarakis committed Jun 25, 2024
1 parent e11e3c9 commit 61a5da5
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 7 deletions.
13 changes: 8 additions & 5 deletions src/app/book/book-api.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { Book } from './book';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
Expand All @@ -7,15 +7,18 @@ import { HttpClient } from '@angular/common/http';
providedIn: 'root'
})
export class BookApiService {
readonly #http = inject(HttpClient);
readonly #baseUrl = 'http://localhost:4730';

constructor(private readonly http: HttpClient) {}

getAll(): Observable<Book[]> {
return this.http.get<Book[]>(`${this.#baseUrl}/books`);
return this.#http.get<Book[]>(`${this.#baseUrl}/books`);
}

getByIsbn(isbn: string): Observable<Book> {
return this.http.get<Book>(`${this.#baseUrl}/books/${isbn}`);
return this.#http.get<Book>(`${this.#baseUrl}/books/${isbn}`);
}

create(book: Partial<Book>): Observable<Book> {
return this.#http.post<Book>('http://localhost:4730/books', book);
}
}
54 changes: 54 additions & 0 deletions src/app/book/book-new/book-new.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<h2>New</h2>

<form [formGroup]="form" (ngSubmit)="submit()">
<label class="form-field">
<span>ISBN</span>
<input formControlName="isbn" />
@if (form.controls.isbn.dirty && form.controls.isbn.hasError('required')) {
<small>Please insert an Author.</small>
}
</label>

<label class="form-field">
<span>Title</span>
<input formControlName="title" />
@if (
form.controls.title.dirty && form.controls.title.hasError('required')
) {
<small>Please insert a title.</small>
}
</label>

<label class="form-field">
<span>Subtitle</span>
<input formControlName="subtitle" />
</label>

<ng-container formArrayName="authors">
@for (author of authors.controls; track index; let index = $index) {
<label class="form-field">
<span>Author</span>
<input [formControlName]="index" />

@if (author.dirty) {
@if (author.hasError('required')) {
<small>Please insert an Author.</small>
} @else if (author.hasError('invalidAuthor')) {
<small>Name must not contain digits</small>
}
}
</label>
<button (click)="deleteAuthor(index)">Remove Author</button>
}
</ng-container>
<button type="button" (click)="addAuthor()">Author hinzufügen</button>

<label class="form-field">
<span>Abstract</span>
<input formControlName="abstract" />
</label>

<div class="form-actions">
<button type="submit" [disabled]="form.invalid">Save</button>
</div>
</form>
31 changes: 31 additions & 0 deletions src/app/book/book-new/book-new.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
:host {
display: block;
margin: 1rem;
}

.form-field {
display: block;
margin-top: 1rem;

> span {
display: block;
margin-bottom: 0.5rem;
}

> input {
padding: 12px 20px 12px 6px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
min-width: 250px;
}
}

.form-field-hint {
display: block;
color: #3c3c3c;
}

.form-actions {
margin-top: 1rem;
}
65 changes: 65 additions & 0 deletions src/app/book/book-new/book-new.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Component, inject } from '@angular/core';
import {
FormArray,
FormControl,
FormGroup,
NonNullableFormBuilder,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { BookApiService } from '../book-api.service';
import { validAuthorName } from '../validators/author.validator';

interface BookForm {
isbn: FormControl<string>;
title: FormControl<string>;
subtitle: FormControl<string>;
authors: FormArray<FormControl<string>>;
abstract: FormControl<string>;
}

@Component({
selector: 'app-book-new',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './book-new.component.html',
styleUrls: ['./book-new.component.scss']
})
export class BookNewComponent {
private readonly formBuilder = inject(NonNullableFormBuilder);
private readonly bookApiService = inject(BookApiService);

form: FormGroup<BookForm> = this.formBuilder.group({
isbn: ['', [Validators.required]],
title: ['', [Validators.required]],
subtitle: [''],
authors: this.formBuilder.array([
['', [Validators.required, validAuthorName()]]
]),
abstract: ['']
});

submit() {
this.bookApiService.create(this.form.getRawValue()).subscribe();
// We need to handle the formArray now for authors separately
// Unfortunately the backend doesn't handle multiple authors yet
const firstAuthor = this.form.getRawValue().authors[0] || 'n/a';
this.bookApiService
.create({ ...this.form.getRawValue(), author: firstAuthor })
.subscribe();
}

get authors(): FormArray {
return this.form.controls.authors;
}

deleteAuthor(authorIndex: number) {
this.authors.removeAt(authorIndex);
}

addAuthor() {
this.authors.push(
this.formBuilder.control('', [Validators.required, validAuthorName()])
);
}
}
4 changes: 4 additions & 0 deletions src/app/book/book.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
(input)="updateBookSearchTerm($event)"
/>

<div>
<a routerLink="new">Create Book</a>
</div>

@for (book of books() | bookFilter: bookSearchTerm; track book.isbn) {
<app-book-card [content]="book" (detailClick)="goToBookDetails($event)" />
}
4 changes: 2 additions & 2 deletions src/app/book/book.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { BookFilterPipe } from './book-filter/book-filter.pipe';
import { Book } from './book';
import { BookApiService } from './book-api.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';
import { Router, RouterLink, RouterLinkActive } from '@angular/router';

@Component({
selector: 'app-book',
standalone: true,
imports: [BookCardComponent, BookFilterPipe],
imports: [BookCardComponent, BookFilterPipe, RouterLink, RouterLinkActive],
templateUrl: './book.component.html',
styleUrl: './book.component.scss'
})
Expand Down
5 changes: 5 additions & 0 deletions src/app/book/book.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import { Routes } from '@angular/router';
import { BookComponent } from './book.component';
import { BookDetailComponent } from './book-detail/book-detail.component';
import { confirmLeaveGuardFn } from './confirm-leave.guard';
import { BookNewComponent } from './book-new/book-new.component';

export const bookRoutes: Routes = [
{
path: '',
component: BookComponent
},
{
path: 'new',
component: BookNewComponent
},
{
path: 'detail/:isbn',
component: BookDetailComponent,
Expand Down
13 changes: 13 additions & 0 deletions src/app/book/validators/author.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms';

export function validAuthorName(): ValidatorFn {
return (control:AbstractControl) : ValidationErrors | null => {
const value = control.value;
if (!value) {
return null;
}

const hasNumeric = /[0-9]+/.test(value);
return hasNumeric ? { invalidAuthor : true }: null;
}
}

0 comments on commit 61a5da5

Please sign in to comment.