Diskussionsthread zur Schulung #1
Replies: 32 comments 2 replies
-
Automatische Migration
|
Beta Was this translation helpful? Give feedback.
-
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
// app.config.ts
// Vorsicht: nicht sofort produktiv nehmen
provideExperimentalZonelessChangeDetection() |
Beta Was this translation helpful? Give feedback.
-
// book-store.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { Book } from './book';
@Injectable({
providedIn: 'root'
})
export class BookStoreService {
http = inject(HttpClient);
getAllBooks(): Observable<Book[]> {
return this.http.get<Book[]>('https://api.angular.schule/books');
}
getSingleBook(isbn: string): Observable<Book> {
return this.http.get<Book>('https://api.angular.schule/books/' + isbn /* + '/slow' */);
}
} |
Beta Was this translation helpful? Give feedback.
-
"test": {
"polyfills": [
"zone.js",
"zone.js/testing"
], import { Book } from './book';
import { BookRatingService } from './book-rating.service';
describe('BookRatingService', () => {
let service: BookRatingService;
let book: Book;
// Arrange
beforeEach(() => {
service = new BookRatingService();
book = {
isbn: '000',
title: 'Test',
description: 'Test',
rating: 3,
price: 99
};
});
it('should rate up a book by one', () => {
// Act
const ratedBook = service.rateUp(book);
// Assert
expect(ratedBook.rating).toBe(4);
});
it('should rate down a book by one', () => {
const ratedBook = service.rateDown(book);
expect(ratedBook.rating).toBe(2);
});
it('should not be allowed to have a rating greater than 5', () => {
book.rating = 5;
const ratedBook = service.rateUp(book);
expect(ratedBook.rating).toBe(5);
});
it('should not be allowed to have a rating smaller than 1', () => {
book.rating = 1;
const ratedBook = service.rateDown(book);
expect(ratedBook.rating).toBe(1);
});
it('should not mutate the book', () => {
const frozenBook = Object.freeze(book);
expect(() => service.rateUp(frozenBook)).not.toThrow();
expect(() => service.rateDown(frozenBook)).not.toThrow();
})
}); |
Beta Was this translation helpful? Give feedback.
-
Viel Drama auf Tech Twitter: microsoft/playwright#27783 |
Beta Was this translation helpful? Give feedback.
-
Jasmine Spione: https://github.com/angular-schule/2023-09-angular-workshop-hannover/blob/main/book-rating/src/app/books/dashboard/dashboard.component.spec.ts |
Beta Was this translation helpful? Give feedback.
-
playwright API testing: https://playwright.dev/docs/api-testing example working-with-api.spec.ts
|
Beta Was this translation helpful? Give feedback.
-
auth.setup.ts
|
Beta Was this translation helpful? Give feedback.
-
Hausaufgabe (bis Montag)Das Buch wurde client-seitig geupdated. Sende die Information zum Server. a) du bist fertig |
Beta Was this translation helpful? Give feedback.
-
Signals for Reactive and Template-based Forms (Open!) |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
import { Component, output } from '@angular/core';
import { Book } from '../shared/book';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-book-form',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './book-form.component.html',
styleUrl: './book-form.component.scss'
})
export class BookFormComponent {
create = output<Book>();
bookForm = new FormGroup({
isbn: new FormControl('', {
nonNullable: true,
validators: [Validators.required, Validators.minLength(3)]
}),
title: new FormControl('', {
nonNullable: true,
validators: [Validators.required]
}),
description: new FormControl('', {
nonNullable: true
})
});
c = this.bookForm.controls;
isInvalid(control: FormControl): boolean {
return control.invalid && control.touched
}
submitForm() {
const newBook: Book = {
...this.bookForm.getRawValue(),
rating: 1,
price: 1
}
this.create.emit(newBook);
this.bookForm.reset();
}
} <form [formGroup]="bookForm" (ngSubmit)="submitForm()">
<div class="form-group">
<label for="isbn">ISBN</label>
<input [formControl]="c.isbn" type="text" class="form-control" id="isbn">
@if (isInvalid(c.isbn)) {
<div class="feedback-error">Die ISBN ist ungültig!</div>
}
</div>
<div class="form-group">
<label for="title">Titel</label>
<input [formControl]="c.title" type="text" class="form-control" id="title">
</div>
<div class="form-group">
<label for="description">Beschreibung</label>
<textarea [formControl]="c.description" class="form-control" id="description"></textarea>
</div>
<button [disabled]="bookForm.invalid" type="submit" class="btn btn-primary mb-4">Erstellen</button>
</form> |
Beta Was this translation helpful? Give feedback.
-
// book-form-component.ts
import { ChangeDetectionStrategy, Component, input, output, effect } from '@angular/core';
import { Book } from '../shared/book';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-book-form',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './book-form.component.html',
styleUrl: './book-form.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BookFormComponent {
create = output<Book>();
edit = output<Book>();
// createOrEdit = output<{
// type: 'edit' | 'create',
// book: Book
// }>()
currentBook = input<Book | undefined>();
updateForm = effect(() => {
// console.log('Current Book', this.currentBook());
const currentBook = this.currentBook();
const isbnControl = this.bookForm.controls.isbn;
if (currentBook) {
this.bookForm.patchValue(currentBook);
isbnControl.disable();
} else {
isbnControl.enable();
}
});
bookForm = new FormGroup({
isbn: new FormControl('', {
nonNullable: true,
validators: [Validators.required, Validators.minLength(3)]
}),
title: new FormControl('', {
nonNullable: true,
validators: [Validators.required]
}),
description: new FormControl('', {
nonNullable: true
})
});
isInvalid(control: FormControl): boolean {
return control.invalid && control.touched
}
submitForm() {
const currentBook = this.currentBook();
if (currentBook) {
const { rating, price } = currentBook;
const changedBook: Book = {
...this.bookForm.getRawValue(),
rating,
price
}
this.edit.emit(changedBook);
} else {
const newBook: Book = {
...this.bookForm.getRawValue(),
rating: 1,
price: 1
}
this.create.emit(newBook);
}
this.bookForm.reset();
}
} <!-- book-form-component.ts -->
import { ChangeDetectionStrategy, Component, input, output, effect } from '@angular/core';
import { Book } from '../shared/book';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-book-form',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './book-form.component.html',
styleUrl: './book-form.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BookFormComponent {
create = output<Book>();
edit = output<Book>();
currentBook = input<Book | undefined>();
updateForm = effect(() => {
// console.log('Current Book', this.currentBook());
const currentBook = this.currentBook();
const isbnControl = this.bookForm.controls.isbn;
if (currentBook) {
this.bookForm.patchValue(currentBook);
isbnControl.disable();
} else {
isbnControl.enable();
}
});
bookForm = new FormGroup({
isbn: new FormControl('', {
nonNullable: true,
validators: [Validators.required, Validators.minLength(3)]
}),
title: new FormControl('', {
nonNullable: true,
validators: [Validators.required]
}),
description: new FormControl('', {
nonNullable: true
})
});
isInvalid(control: FormControl): boolean {
return control.invalid && control.touched
}
submitForm() {
const currentBook = this.currentBook();
if (currentBook) {
const { rating, price } = currentBook;
const changedBook: Book = {
...this.bookForm.getRawValue(),
rating,
price
}
this.edit.emit(changedBook);
} else {
const newBook: Book = {
...this.bookForm.getRawValue(),
rating: 1,
price: 1
}
this.create.emit(newBook);
}
this.bookForm.reset();
}
} |
Beta Was this translation helpful? Give feedback.
-
// dashboard.component.ts
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { BookComponent } from '../book/book.component';
import { Book } from '../shared/book';
import { BookRatingService } from '../shared/book-rating.service';
import { BookStoreService } from '../shared/book-store.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { BookFormComponent } from '../book-form/book-form.component';
@Component({
selector: 'app-dashboard',
standalone: true,
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss',
imports: [BookComponent, BookFormComponent],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent {
rs = inject(BookRatingService);
bs = inject(BookStoreService);
//books = toSignal(this.bs.getAllBooks(), { initialValue: [] });
books = signal<Book[]>([]);
currentBook = signal<Book | undefined>(undefined);
constructor() {
this.bs.getAllBooks().subscribe(books => this.books.set(books))
}
zeigeEinBuchAn() {
alert(this.books()[0]?.title);
}
doRateUp(book: Book) {
const ratedBook = this.rs.rateUp(book);
this.updateAndSortList(ratedBook);
}
doRateDown(book: Book) {
const ratedBook = this.rs.rateDown(book);
this.updateAndSortList(ratedBook);
}
updateAndSortList(changedBook: Book) {
this.books.update(books => books
.map(b => b.isbn === changedBook.isbn ? changedBook : b)
.sort((a, b) => b.rating - a.rating));
// TODO: Buch zum Server senden (Hausaufgabe)
}
addBook(book: Book) {
this.books.update(books => [...books, book]);
}
changeToEditMode(book: Book) {
this.currentBook.set(book)
}
changeBook(changedBook: Book) {
this.updateAndSortList(changedBook);
this.currentBook.set(undefined);
}
} <!-- dashboard.component.html -->
<app-book-form
(create)="addBook($event)"
(edit)="changeBook($event)"
[currentBook]="currentBook()"
/>
@for (book of books(); track book) {
<app-book
(rateUp)="doRateUp($event)"
(rateDown)="doRateDown($event)"
[book]="book"
(edit)="changeToEditMode($event)"
/>
} @empty {
Keine Bücher vorhanden
}
<button (click)="zeigeEinBuchAn()">Zeige 0. Buch an</button>
<p>
<strong>Anzahl Bücher:</strong> {{ books().length }}
</p> |
Beta Was this translation helpful? Give feedback.
-
ng add @ngrx/store
ng add @ngrx/effects
ng add @ngrx/store-devtools
ng add @ngrx/schematics
ng g feature books/store/book --entity=false --api --defaults |
Beta Was this translation helpful? Give feedback.
-
// book.reducer.ts
import { createFeature, createReducer, on } from '@ngrx/store';
import { BookActions } from './book.actions';
import { Book } from '../shared/book';
export const bookFeatureKey = 'book';
export interface State {
books: Book[],
loading: boolean
}
export const initialState: State = {
books: [],
loading: false
};
export const reducer = createReducer(
initialState,
on(BookActions.loadBooks, state => ({
...state,
loading: true
})),
on(BookActions.loadBooksSuccess, (state, { books }) => ({
... state,
loading: false,
books
})),
on(BookActions.loadBooksFailure, state => ({
...state,
loading: false,
books: []
}))
);
export const bookFeature = createFeature({
name: bookFeatureKey,
reducer,
}); |
Beta Was this translation helpful? Give feedback.
-
Hands On (Gruppenarbeit)
|
Beta Was this translation helpful? Give feedback.
-
Im Portal findet ihr neue Slides & Handouts! |
Beta Was this translation helpful? Give feedback.
-
1// book-rating.service.spec.ts
import { Book } from './book';
import { BookRatingService } from './book-rating.service';
describe('BookRatingService', () => {
let book: Book;
// Arrange
beforeEach(() => {
book = {
isbn: '000',
title: 'Test',
description: 'Test',
rating: 3,
price: 99
};
});
it('should rate up a book by one', () => {
// Act
const ratedBook = BookRatingService.rateUp(book);
// Assert
expect(ratedBook.rating).toBe(4);
});
it('should rate down a book by one', () => {
const ratedBook = BookRatingService.rateDown(book);
expect(ratedBook.rating).toBe(2);
});
it('should not be allowed to have a rating greater than 5', () => {
book.rating = 5;
const ratedBook = BookRatingService.rateUp(book);
expect(ratedBook.rating).toBe(5);
});
it('should not be allowed to have a rating smaller than 1', () => {
book.rating = 1;
const ratedBook = BookRatingService.rateDown(book);
expect(ratedBook.rating).toBe(1);
});
it('should not mutate the book', () => {
const frozenBook = Object.freeze(book);
expect(() => BookRatingService.rateUp(frozenBook)).not.toThrow();
expect(() => BookRatingService.rateDown(frozenBook)).not.toThrow();
})
}); |
Beta Was this translation helpful? Give feedback.
-
2// book.selectors.spec.ts
import * as fromBook from './book.reducer';
import { selectBookState } from './book.selectors';
describe('Book Selectors', () => {
it('should select the feature state', () => {
const result = selectBookState({
[fromBook.bookFeatureKey]: {}
});
// expect(result).toEqual({}); // diese Zeile auskommentieren
});
}); |
Beta Was this translation helpful? Give feedback.
-
3Alle Tests für |
Beta Was this translation helpful? Give feedback.
-
4
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from './books/store/book.reducer';
await TestBed.configureTestingModule({
imports: [
AppComponent
],
providers: [
provideMockStore({ initialState }) // NEU
] |
Beta Was this translation helpful? Give feedback.
-
5
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
TestBed.configureTestingModule({
providers: [
BookEffects,
provideMockActions(() => actions$),
provideHttpClient(), // NEU
provideHttpClientTesting() // NEU
]
}); |
Beta Was this translation helpful? Give feedback.
-
import { Book } from '../shared/book';
import { BookActions } from './book.actions';
import { reducer, initialState } from './book.reducer';
describe('Book Reducer', () => {
it('should set loading flag to `true` on loadBooks action', () => {
const action = BookActions.loadBooks();
const newState = reducer(initialState, action);
expect(newState.loading).toBeTrue();
});
it('should set books on loadBooksSuccess action', () => {
const newBooks: Book[] = [];
const action = BookActions.loadBooksSuccess({ books: newBooks });
const oldState = {
books: [],
loading: true
};
// ODER
// const oldState = reducer(initialState, BookActions.loadBooks());
const newState = reducer(oldState, action);
expect(newState.loading).toBeFalse();
expect(newState.books).toBe(newBooks);
});
// Hands On (Gruppenarbeit):
// 1. Teste den Reducer, der auf die create-Action reagiert
// 2. Teste den Reducer, der auf die rateUp-Action reagiert
}); |
Beta Was this translation helpful? Give feedback.
-
🎮 NEU: RxJS PlaygroundDu kannst dir entweder
cd rxjs-playground
npm install
ng serve Öffne den Browser unter der URL http://localhost:4300 (!), um die Anwendung zu sehen. |
Beta Was this translation helpful? Give feedback.
-
Wie versprochen, zeige ich euch hier zwei Möglichkeiten, um in einem Effekt auf die Werte des Stores zugreifen zu können! 1. mit
|
Beta Was this translation helpful? Give feedback.
-
Everything you need to know about the resource APIhttps://push-based.io/article/everything-you-need-to-know-about-the-resource-api |
Beta Was this translation helpful? Give feedback.
-
bs = inject(BookStoreService);
book$ = inject(ActivatedRoute).paramMap.pipe(
map(paramMap => paramMap.get('isbn')!),
switchMap(isbn => this.bs.getSingleBook(isbn).pipe(
retry({
count: 3,
delay: 1000
}),
// catchError((e: HttpErrorResponse) => of({
// title: 'FEHLER',
// description: e.message
// }))
catchError((e: HttpErrorResponse) => EMPTY)
))
); |
Beta Was this translation helpful? Give feedback.
-
Hands OnBitte einen PR senden für das |
Beta Was this translation helpful? Give feedback.
-
📕 Neu im Blog: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Herzlich Willkommen! 🎉 Hier können wir während der Schulung Links und Codeschnipsel teilen.
Beta Was this translation helpful? Give feedback.
All reactions