Skip to content

Commit 51c4afd

Browse files
committed
rewrote with signals
1 parent 81aeddd commit 51c4afd

19 files changed

+208
-250
lines changed

src/app/core/auth/auth.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ <h1 class="text-xs-center">{{ title }}</h1>
1212
<a [routerLink]="['/register']">Need an account?</a>
1313
}
1414
</p>
15-
<app-list-errors [errors]="errors" />
15+
<app-list-errors [errors]="errors()" />
1616
<form [formGroup]="authForm" (ngSubmit)="submitForm()">
17-
<fieldset [disabled]="isSubmitting">
17+
<fieldset [disabled]="isSubmitting()">
1818
<fieldset class="form-group">
1919
@if (authType === 'register') {
2020
<input

src/app/core/auth/auth.component.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal } from '@angular/core';
22
import { Validators, FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
33
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
44
import { ListErrorsComponent } from '../../shared/components/list-errors.component';
@@ -21,11 +21,10 @@ interface AuthForm {
2121
export default class AuthComponent implements OnInit {
2222
authType = '';
2323
title = '';
24-
errors: Errors = { errors: {} };
25-
isSubmitting = false;
24+
errors = signal<Errors>({ errors: {} });
25+
isSubmitting = signal(false);
2626
authForm: FormGroup<AuthForm>;
2727
destroyRef = inject(DestroyRef);
28-
cdr = inject(ChangeDetectorRef);
2928

3029
constructor(
3130
private readonly route: ActivatedRoute,
@@ -59,8 +58,8 @@ export default class AuthComponent implements OnInit {
5958
}
6059

6160
submitForm(): void {
62-
this.isSubmitting = true;
63-
this.errors = { errors: {} };
61+
this.isSubmitting.set(true);
62+
this.errors.set({ errors: {} });
6463

6564
let observable =
6665
this.authType === 'login'
@@ -76,9 +75,8 @@ export default class AuthComponent implements OnInit {
7675
observable.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
7776
next: () => void this.router.navigate(['/']),
7877
error: err => {
79-
this.errors = err;
80-
this.isSubmitting = false;
81-
this.cdr.markForCheck();
78+
this.errors.set(err);
79+
this.isSubmitting.set(false);
8280
},
8381
});
8482
}
Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
import {
2-
ChangeDetectorRef,
3-
DestroyRef,
4-
Directive,
5-
inject,
6-
Input,
7-
OnInit,
8-
TemplateRef,
9-
ViewContainerRef,
10-
} from '@angular/core';
1+
import { DestroyRef, Directive, inject, Input, OnInit, signal, TemplateRef, ViewContainerRef } from '@angular/core';
112
import { UserService } from './services/user.service';
123
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
134

@@ -17,33 +8,31 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
178
})
189
export class IfAuthenticatedDirective<T> implements OnInit {
1910
destroyRef = inject(DestroyRef);
20-
cdr = inject(ChangeDetectorRef);
2111
constructor(
2212
private templateRef: TemplateRef<T>,
2313
private userService: UserService,
2414
private viewContainer: ViewContainerRef,
2515
) {}
2616

27-
condition: boolean = false;
28-
hasView = false;
17+
condition = signal(false);
18+
hasView = signal(false);
2919

3020
ngOnInit() {
3121
this.userService.isAuthenticated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((isAuthenticated: boolean) => {
32-
const authRequired = isAuthenticated && this.condition;
33-
const unauthRequired = !isAuthenticated && !this.condition;
22+
const authRequired = isAuthenticated && this.condition();
23+
const unauthRequired = !isAuthenticated && !this.condition();
3424

35-
if ((authRequired || unauthRequired) && !this.hasView) {
25+
if ((authRequired || unauthRequired) && !this.hasView()) {
3626
this.viewContainer.createEmbeddedView(this.templateRef);
37-
this.hasView = true;
38-
} else if (this.hasView) {
27+
this.hasView.set(true);
28+
} else if (this.hasView()) {
3929
this.viewContainer.clear();
40-
this.hasView = false;
30+
this.hasView.set(false);
4131
}
42-
this.cdr.markForCheck();
4332
});
4433
}
4534

4635
@Input() set ifAuthenticated(condition: boolean) {
47-
this.condition = condition;
36+
this.condition.set(condition);
4837
}
4938
}

src/app/features/article/components/article-list.component.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {
22
ChangeDetectionStrategy,
3-
ChangeDetectorRef,
43
Component,
54
DestroyRef,
65
EventEmitter,
76
inject,
87
Input,
98
OnChanges,
109
Output,
10+
signal,
1111
SimpleChanges,
1212
} from '@angular/core';
1313
import { ArticlesService } from '../services/articles.service';
@@ -22,13 +22,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2222
@Component({
2323
selector: 'app-article-list',
2424
template: `
25-
@if (loading === LoadingState.LOADING) {
25+
@if (loading() === LoadingState.LOADING) {
2626
<div class="article-preview">Loading articles...</div>
2727
}
2828
29-
@if (loading === LoadingState.LOADED) {
30-
@for (article of results; track article.slug) {
31-
<app-article-preview [article]="article" />
29+
@if (loading() === LoadingState.LOADED) {
30+
@for (article of results(); track article.slug) {
31+
<app-article-preview [articleInput]="article" />
3232
} @empty {
3333
<div class="article-preview empty-feed-message">
3434
@if (isFollowingFeed) {
@@ -42,8 +42,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4242
4343
<nav>
4444
<ul class="pagination">
45-
@for (pageNumber of totalPages; track pageNumber) {
46-
<li class="page-item" [ngClass]="{ active: pageNumber === page }">
45+
@for (pageNumber of totalPages(); track pageNumber) {
46+
<li class="page-item" [ngClass]="{ active: pageNumber === page() }">
4747
<button class="page-link" (click)="setPageTo(pageNumber)">
4848
{{ pageNumber }}
4949
</button>
@@ -63,13 +63,12 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6363
})
6464
export class ArticleListComponent implements OnChanges {
6565
query!: ArticleListConfig;
66-
results: Article[] = [];
67-
page = 1;
68-
totalPages: Array<number> = [];
69-
loading = LoadingState.NOT_LOADED;
66+
results = signal<Article[]>([]);
67+
page = signal(1);
68+
totalPages = signal<number[]>([]);
69+
loading = signal(LoadingState.NOT_LOADED);
7070
LoadingState = LoadingState;
7171
destroyRef = inject(DestroyRef);
72-
cdr = inject(ChangeDetectorRef);
7372

7473
@Input() limit!: number;
7574
@Input() config!: ArticleListConfig;
@@ -85,12 +84,12 @@ export class ArticleListComponent implements OnChanges {
8584
this.query = configChange.currentValue;
8685
// Only reset page if currentPage wasn't also provided in this change
8786
if (!pageChange?.currentValue) {
88-
this.page = 1;
87+
this.page.set(1);
8988
}
9089
}
9190

9291
if (pageChange?.currentValue) {
93-
this.page = pageChange.currentValue;
92+
this.page.set(pageChange.currentValue);
9493
}
9594

9695
// Run query if we have a config and either config or page changed
@@ -102,33 +101,34 @@ export class ArticleListComponent implements OnChanges {
102101
constructor(private articlesService: ArticlesService) {}
103102

104103
setPageTo(pageNumber: number) {
105-
if (pageNumber !== this.page) {
106-
this.page = pageNumber;
104+
if (pageNumber !== this.page()) {
105+
this.page.set(pageNumber);
107106
this.pageChange.emit(pageNumber);
108107
this.runQuery();
109108
}
110109
}
111110

112111
runQuery() {
113-
this.loading = LoadingState.LOADING;
114-
this.results = [];
112+
this.loading.set(LoadingState.LOADING);
113+
this.results.set([]);
115114

116115
// Create limit and offset filter (if necessary)
117116
if (this.limit) {
118117
this.query.filters.limit = this.limit;
119-
this.query.filters.offset = this.limit * (this.page - 1);
118+
this.query.filters.offset = this.limit * (this.page() - 1);
120119
}
121120

122121
this.articlesService
123122
.query(this.query)
124123
.pipe(takeUntilDestroyed(this.destroyRef))
125124
.subscribe(data => {
126-
this.loading = LoadingState.LOADED;
127-
this.results = data.articles;
125+
this.loading.set(LoadingState.LOADED);
126+
this.results.set(data.articles);
128127

129128
// Used from http://www.jstips.co/en/create-range-0...n-easily-using-one-line/
130-
this.totalPages = Array.from(new Array(Math.ceil(data.articlesCount / this.limit)), (val, index) => index + 1);
131-
this.cdr.markForCheck();
129+
this.totalPages.set(
130+
Array.from(new Array(Math.ceil(data.articlesCount / this.limit)), (val, index) => index + 1),
131+
);
132132
});
133133
}
134134
}
Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core';
22
import { Article } from '../models/article.model';
33
import { ArticleMetaComponent } from './article-meta.component';
44
import { RouterLink } from '@angular/router';
@@ -9,18 +9,18 @@ import { FavoriteButtonComponent } from './favorite-button.component';
99
selector: 'app-article-preview',
1010
template: `
1111
<div class="article-preview">
12-
<app-article-meta [article]="article">
13-
<app-favorite-button [article]="article" (toggle)="toggleFavorite($event)" class="pull-xs-right">
14-
{{ article.favoritesCount }}
12+
<app-article-meta [article]="article()">
13+
<app-favorite-button [article]="article()" (toggle)="toggleFavorite($event)" class="pull-xs-right">
14+
{{ article().favoritesCount }}
1515
</app-favorite-button>
1616
</app-article-meta>
1717
18-
<a [routerLink]="['/article', article.slug]" class="preview-link">
19-
<h1>{{ article.title }}</h1>
20-
<p>{{ article.description }}</p>
18+
<a [routerLink]="['/article', article().slug]" class="preview-link">
19+
<h1>{{ article().title }}</h1>
20+
<p>{{ article().description }}</p>
2121
<span>Read more...</span>
2222
<ul class="tag-list">
23-
@for (tag of article.tagList; track tag) {
23+
@for (tag of article().tagList; track tag) {
2424
<li class="tag-default tag-pill tag-outline">
2525
{{ tag }}
2626
</li>
@@ -33,17 +33,18 @@ import { FavoriteButtonComponent } from './favorite-button.component';
3333
changeDetection: ChangeDetectionStrategy.OnPush,
3434
})
3535
export class ArticlePreviewComponent {
36-
cdr = inject(ChangeDetectorRef);
37-
@Input() article!: Article;
36+
article = signal<Article>(null!);
3837

39-
toggleFavorite(favorited: boolean): void {
40-
this.article.favorited = favorited;
38+
@Input({ required: true })
39+
set articleInput(value: Article) {
40+
this.article.set(value);
41+
}
4142

42-
if (favorited) {
43-
this.article.favoritesCount++;
44-
} else {
45-
this.article.favoritesCount--;
46-
}
47-
this.cdr.markForCheck();
43+
toggleFavorite(favorited: boolean): void {
44+
this.article.update(article => ({
45+
...article,
46+
favorited,
47+
favoritesCount: favorited ? article.favoritesCount + 1 : article.favoritesCount - 1,
48+
}));
4849
}
4950
}

src/app/features/article/components/favorite-button.component.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {
22
ChangeDetectionStrategy,
3-
ChangeDetectorRef,
43
Component,
54
DestroyRef,
65
EventEmitter,
76
inject,
87
Input,
98
Output,
9+
signal,
1010
} from '@angular/core';
1111
import { Router } from '@angular/router';
1212
import { EMPTY, switchMap } from 'rxjs';
@@ -22,7 +22,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2222
<button
2323
class="btn btn-sm"
2424
[ngClass]="{
25-
disabled: isSubmitting,
25+
disabled: isSubmitting(),
2626
'btn-outline-primary': !article.favorited,
2727
'btn-primary': article.favorited,
2828
}"
@@ -36,8 +36,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3636
})
3737
export class FavoriteButtonComponent {
3838
destroyRef = inject(DestroyRef);
39-
cdr = inject(ChangeDetectorRef);
40-
isSubmitting = false;
39+
isSubmitting = signal(false);
4140

4241
@Input() article!: Article;
4342
@Output() toggle = new EventEmitter<boolean>();
@@ -49,7 +48,7 @@ export class FavoriteButtonComponent {
4948
) {}
5049

5150
toggleFavorite(): void {
52-
this.isSubmitting = true;
51+
this.isSubmitting.set(true);
5352

5453
this.userService.isAuthenticated
5554
.pipe(
@@ -69,13 +68,11 @@ export class FavoriteButtonComponent {
6968
)
7069
.subscribe({
7170
next: () => {
72-
this.isSubmitting = false;
71+
this.isSubmitting.set(false);
7372
this.toggle.emit(!this.article.favorited);
74-
this.cdr.markForCheck();
7573
},
7674
error: () => {
77-
this.isSubmitting = false;
78-
this.cdr.markForCheck();
75+
this.isSubmitting.set(false);
7976
},
8077
});
8178
}

0 commit comments

Comments
 (0)