dirty-check.component.ts (optional)
- implement dirty-check.component.ts
src/app/ui/atoms/dirty-check.component.ts
@Component({
selector: 'app-dirty-check',
template: `{{ check() }}`
})
export class DirtyCheckComponent {
private _checked = 0;
check() {
return this._checked++;
}
}
// obsolete in v13 (experimental)
@NgModule({
imports: [],
exports: [DirtyCheckComponent],
declarations: [DirtyCheckComponent],
})
export class DirtyCheckComponentModule {}
movie-list.component
- add
app-dirty-check
to template - run
- turn on CD
- data still shown, checked() count is 1
movie-list-page.component
- add
app-dirty-check
to template - run
- turn on CD
- movies missing, how to fix?
cdRef#markForCheck
movies$
=> async pipe
app-shell.component
-
add
app-dirty-check
to template -
run
-
turn on CD
-
genres missing, how to fix?
cdRef#markForCheck
genres$
=> async pipe
-
app.component
-
add
app-dirty-check
to template -
run
-
turn on CD
-
run again
(optional) trackByProp util
- implement
trackByProp
util function src/app/shared/utils/track-by.ts
export const trackByProp: <T>(prop: keyof T) => TrackByFunction<T> =
<T>(prop: keyof T) =>
(_: number, item: T) =>
item[prop];
movie-list.component.ts
- add
app-dirty-check
to movie-item template - run
- add trackBy to
*ngFor="let movie of movies;"
- run again
// use util function
trackMovie = trackByProp<Movie>('id');
// or implement it plain
movieById(_: number, movie: Movie) {
return movie.id;
}
app-shell.component.ts
- add
app-dirty-check
to genre-item template - run
- add trackBy to
*ngFor="let genre of genres;"
- run again
// use util function
trackGenre = trackByProp<MovieGenreModel>('name');
// or implement it plain
trackGenre(index: number, genre: MovieGenreModel) {
return genre.name;
}
app-shell.component
- activeRoute$ | async pipe usage (ngFor)
- shareReplay()
- *ngIf= (o$ | async) as
- subscribe => genre = result
move-list-page.component
subscription handling:
- unsubscribe
manage list state: loading, error, refresh * start: https://stackblitz.com/edit/angular-ivy-xlcrcn * end: https://stackblitz.com/edit/angular-ivy-r84fjs
list state & refresh
per-item update
extend star-rating.component.ts
<span class="star" *ngIf="selected">
{{ selected }}
</span>
<span
*ngFor="let fill of stars; let i = index; trackBy: trackByIndex"
class="star"
(click)="starClicked($event, i + 1)"
[ngClass]="{
'star-half': fill === 0,
'star-empty': fill === -1
}"
style
>★</span>
selected = 0;
@Output() starSelected = new EventEmitter<number>();
starClicked(event: MouseEvent, star: number): void {
// don't mess up with click event for routing
event.preventDefault();
event.stopPropagation();
this.selected = star;
}
pass-through @Output() starSelected
from star-rating.component.ts
to movie-list-page.component.ts
.
// movie-list.component.ts
// we pass through `starSelected` via `ratingUpdate` to the parent component
@Output() ratingUpdated = new EventEmitter<number>();
<!-- movie-list.component.html -->
<app-star-rating
(starSelected)="ratingUpdated.next($event)"
[rating]='movie.vote_average'></app-star-rating>
// movie-list-page.component.ts
// we pass through `starSelected` via `ratingUpdate` to the parent component
ratingUpdated = new Subject<{ movie, rating }>();
state.connect(
ratingUpdate.pipe(
mergeMap(e => service.updateMovie(e))
)
)
<!-- movie-list-page.component.html -->
<app-movie-list (ratingUpdate)="ratingUpdated.next($event)"></app-movie-list>
pagination & filter:
movie-state
introduce MovieDataState
- movies:
- loading
- error
- re-fetch
- search
- filter
- pagination
- genres:
- share
auth
- introduce auth state
- implement login/logout flow, reflect in the UI
- implement auth interceptor
dependency injection
- introduce abstraction layer (port-pattern)
movie-list.component.ts
- add header outlet
ng-content select=".header"
- user header in movie-list-page.component:
<app-movie-list>
<div class="header">
<h1 class="title">{{ title || '' }}</h1>
<p class="subtitle">movies</p>
</div>
</app-movie-list>
star-rating.component.ts
- make custom star template
- implement
isAuthed
Directive => structural directive
- create nav-item-icon.component from svg in app-shell.component
https://stackblitz.com/edit/rxjs-flattening-operators-example
- migrate to nx: npx nx init-workspace
- extract shared libraries
- use affected commands
- watch dependency graph
- migrate libs
- setup module boundaries
- affected
- fix boundary errors
- implement custom schematic
- run custom schematic