Skip to content

Commit d6cc1c7

Browse files
feat: finalize migration to Signals
1 parent 8a5d389 commit d6cc1c7

23 files changed

+209
-186
lines changed

projects/example-app/src/app/app.config.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import { rootReducers, metaReducers } from '@example-app/reducers';
1111

1212
import { APP_ROUTES } from '@example-app/app.routing';
1313
import { UserEffects, RouterEffects } from '@example-app/core/effects';
14-
import { provideRouter, withHashLocation } from '@angular/router';
14+
import {
15+
provideRouter,
16+
withComponentInputBinding,
17+
withHashLocation,
18+
} from '@angular/router';
1519
import { AuthEffects } from './auth/effects';
1620
import { provideAuth } from '@example-app/auth/reducers';
1721
import { provideLayout } from '@example-app/core/reducers/layout.reducer';
@@ -20,7 +24,7 @@ export const appConfig: ApplicationConfig = {
2024
providers: [
2125
provideAnimationsAsync(),
2226
provideHttpClient(),
23-
provideRouter(APP_ROUTES, withHashLocation()),
27+
provideRouter(APP_ROUTES, withHashLocation(), withComponentInputBinding()),
2428

2529
/**
2630
* provideStore() is imported once in the root providers, accepting a reducer

projects/example-app/src/app/app.routing.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import { NotFoundPageComponent } from '@example-app/core/containers';
66
export const APP_ROUTES: Routes = [
77
{
88
path: 'login',
9-
loadChildren: () =>
10-
import('@example-app/auth/auth.routes').then((m) => m.AUTH_ROUTES),
9+
loadChildren: () => import('@example-app/auth/auth.routes'),
1110
},
1211
{ path: '', redirectTo: '/books', pathMatch: 'full' },
1312
{
1413
path: 'books',
15-
loadChildren: () =>
16-
import('@example-app/books/books.routes').then((m) => m.BOOKS_ROUTES),
14+
loadChildren: () => import('@example-app/books/books.routes'),
1715
canActivate: [authGuard],
1816
},
1917
{
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Routes } from '@angular/router';
22
import { LoginPageComponent } from '@example-app/auth/containers';
33

4-
export const AUTH_ROUTES: Routes = [
4+
export default [
55
{
66
path: '',
77
component: LoginPageComponent,
88
data: { title: 'Login' },
99
},
10-
];
10+
] satisfies Routes;

projects/example-app/src/app/auth/components/__snapshots__/login-form.component.spec.ts.snap

+25-13
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
exports[`Login Page should compile 1`] = `
44
<bc-login-form
5-
errorMessage="null"
5+
errorMessage={[Function Function]}
66
form={[Function FormGroup]}
7-
submitted={[Function EventEmitter_]}
7+
pending={[Function Function]}
8+
pendingEffect={[Function EffectHandle]}
9+
submitted={[Function OutputEmitterRef]}
810
>
911
<mat-card
1012
class="mat-mdc-card mdc-card"
@@ -149,9 +151,11 @@ exports[`Login Page should compile 1`] = `
149151

150152
exports[`Login Page should disable the form if pending 1`] = `
151153
<bc-login-form
152-
errorMessage="null"
154+
errorMessage={[Function Function]}
153155
form={[Function FormGroup]}
154-
submitted={[Function EventEmitter_]}
156+
pending={[Function Function]}
157+
pendingEffect={[Function EffectHandle]}
158+
submitted={[Function OutputEmitterRef]}
155159
>
156160
<mat-card
157161
class="mat-mdc-card mdc-card"
@@ -165,16 +169,19 @@ exports[`Login Page should disable the form if pending 1`] = `
165169
class="mat-mdc-card-content"
166170
>
167171
<form
168-
class="ng-untouched ng-pristine"
172+
class="ng-untouched ng-pristine ng-valid"
169173
novalidate=""
170174
>
171175
<p>
172176
<mat-form-field
173-
class="mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-disabled mat-form-field-no-animations mat-form-field-appearance-fill mat-primary ng-untouched ng-pristine"
177+
class="mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-no-animations mat-form-field-appearance-fill mat-primary ng-untouched ng-pristine ng-valid"
174178
>
175179
<div
176-
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--disabled"
180+
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label"
177181
>
182+
<div
183+
class="mat-mdc-form-field-focus-overlay"
184+
/>
178185
<div
179186
class="mat-mdc-form-field-flex"
180187
>
@@ -184,7 +191,7 @@ exports[`Login Page should disable the form if pending 1`] = `
184191
<input
185192
aria-invalid="false"
186193
aria-required="false"
187-
class="mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine cdk-text-field-autofill-monitored"
194+
class="mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine ng-valid cdk-text-field-autofill-monitored"
188195
data-testid="username"
189196
disabled=""
190197
formcontrolname="username"
@@ -215,11 +222,14 @@ exports[`Login Page should disable the form if pending 1`] = `
215222
</p>
216223
<p>
217224
<mat-form-field
218-
class="mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-disabled mat-form-field-no-animations mat-form-field-appearance-fill mat-primary ng-untouched ng-pristine"
225+
class="mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-no-animations mat-form-field-appearance-fill mat-primary ng-untouched ng-pristine ng-valid"
219226
>
220227
<div
221-
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--disabled"
228+
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label"
222229
>
230+
<div
231+
class="mat-mdc-form-field-focus-overlay"
232+
/>
223233
<div
224234
class="mat-mdc-form-field-flex"
225235
>
@@ -229,7 +239,7 @@ exports[`Login Page should disable the form if pending 1`] = `
229239
<input
230240
aria-invalid="false"
231241
aria-required="false"
232-
class="mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine cdk-text-field-autofill-monitored"
242+
class="mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine ng-valid cdk-text-field-autofill-monitored"
233243
data-testid="password"
234244
disabled=""
235245
formcontrolname="password"
@@ -292,9 +302,11 @@ exports[`Login Page should disable the form if pending 1`] = `
292302

293303
exports[`Login Page should display an error message if provided 1`] = `
294304
<bc-login-form
295-
errorMessage={[Function String]}
305+
errorMessage={[Function Function]}
296306
form={[Function FormGroup]}
297-
submitted={[Function EventEmitter_]}
307+
pending={[Function Function]}
308+
pendingEffect={[Function EffectHandle]}
309+
submitted={[Function OutputEmitterRef]}
298310
>
299311
<mat-card
300312
class="mat-mdc-card mdc-card"

projects/example-app/src/app/auth/components/login-form.component.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('Login Page', () => {
1616
});
1717

1818
fixture = TestBed.createComponent(LoginFormComponent);
19+
fixture.componentRef.setInput('pending', false);
1920
instance = fixture.componentInstance;
2021
});
2122

@@ -38,15 +39,15 @@ describe('Login Page', () => {
3839
});
3940

4041
it('should disable the form if pending', () => {
41-
instance.pending = true;
42+
fixture.componentRef.setInput('pending', true);
4243

4344
fixture.detectChanges();
4445

4546
expect(fixture).toMatchSnapshot();
4647
});
4748

4849
it('should display an error message if provided', () => {
49-
instance.errorMessage = 'Invalid credentials';
50+
fixture.componentRef.setInput('errorMessage', 'Invalid credentials');
5051

5152
fixture.detectChanges();
5253

projects/example-app/src/app/auth/components/login-form.component.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { Component, Input, Output, EventEmitter } from '@angular/core';
1+
import {
2+
Component,
3+
Input,
4+
Output,
5+
EventEmitter,
6+
input,
7+
untracked,
8+
effect,
9+
output,
10+
} from '@angular/core';
211
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
312
import { Credentials } from '@example-app/auth/models';
413
import { MaterialModule } from '@example-app/material';
@@ -36,9 +45,9 @@ import { MaterialModule } from '@example-app/material';
3645
</mat-form-field>
3746
</p>
3847
39-
@if (errorMessage) {
48+
@if (errorMessage()) {
4049
<p class="login-error">
41-
{{ errorMessage }}
50+
{{ errorMessage() }}
4251
</p>
4352
}
4453
@@ -87,18 +96,17 @@ import { MaterialModule } from '@example-app/material';
8796
],
8897
})
8998
export class LoginFormComponent {
90-
@Input()
91-
set pending(isPending: boolean) {
92-
if (isPending) {
93-
this.form.disable();
94-
} else {
95-
this.form.enable();
96-
}
97-
}
99+
readonly pending = input.required<boolean>();
100+
101+
private readonly pendingEffect = effect(() => {
102+
const pending = this.pending();
103+
104+
untracked(() => (pending ? this.form.disable() : this.form.enable()));
105+
});
98106

99-
@Input() errorMessage: string | null = null;
107+
readonly errorMessage = input<string | null>(null);
100108

101-
@Output() submitted = new EventEmitter<Credentials>();
109+
submitted = output<Credentials>();
102110

103111
protected readonly form: FormGroup = new FormGroup({
104112
username: new FormControl('ngrx'),

projects/example-app/src/app/auth/containers/__snapshots__/login-page.component.spec.ts.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
exports[`Login Page should compile 1`] = `
44
<bc-login-page
5-
error$={[Function _Store]}
6-
pending$={[Function _Store]}
7-
store={[Function _MockStore]}
5+
error={[Function Function]}
6+
pending={[Function Function]}
7+
store={[Function MockStore]}
88
>
99
<bc-login-form>
1010
<mat-card

projects/example-app/src/app/auth/containers/login-page.component.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('Login Page', () => {
5252
});
5353

5454
it('should dispatch a login event on submit', () => {
55-
const credentials: any = {};
55+
const credentials = { username: 'ngrx', password: 'rocks' };
5656
const action = LoginPageActions.login({ credentials });
5757

5858
instance.onSubmit(credentials);

projects/example-app/src/app/auth/containers/login-page.component.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@ import { AsyncPipe } from '@angular/common';
1313
template: `
1414
<bc-login-form
1515
(submitted)="onSubmit($event)"
16-
[pending]="(pending$ | async)!"
17-
[errorMessage]="error$ | async"
16+
[pending]="pending()"
17+
[errorMessage]="error()"
1818
>
1919
</bc-login-form>
2020
`,
21-
styles: [],
2221
})
2322
export class LoginPageComponent {
24-
private store = inject(Store);
23+
private readonly store = inject(Store);
2524

26-
protected readonly pending$ = this.store.select(
25+
protected readonly pending = this.store.selectSignal(
2726
fromAuth.selectLoginPagePending
2827
);
29-
protected readonly error$ = this.store.select(fromAuth.selectLoginPageError);
28+
protected readonly error = this.store.selectSignal(
29+
fromAuth.selectLoginPageError
30+
);
3031

3132
onSubmit(credentials: Credentials) {
3233
this.store.dispatch(LoginPageActions.login({ credentials }));

projects/example-app/src/app/books/books.routes.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { bookExistsGuard } from '@example-app/books/guards';
99
import { provideBooks } from '@example-app/books/reducers';
1010

11-
export const BOOKS_ROUTES: Routes = [
11+
export default [
1212
{
1313
path: '',
1414
providers: [provideBooks()],
@@ -31,4 +31,4 @@ export const BOOKS_ROUTES: Routes = [
3131
},
3232
],
3333
},
34-
];
34+
] satisfies Routes;

projects/example-app/src/app/books/components/book-authors.component.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Input } from '@angular/core';
1+
import { Component, computed, input, Input } from '@angular/core';
22

33
import { Book } from '@example-app/books/models';
44
import { MaterialModule } from '@example-app/material';
@@ -11,7 +11,7 @@ import { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';
1111
template: `
1212
<h5 mat-subheader>Written By:</h5>
1313
<span>
14-
{{ authors | bcAddCommas }}
14+
{{ authors() | bcAddCommas }}
1515
</span>
1616
`,
1717
styles: [
@@ -23,9 +23,9 @@ import { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';
2323
],
2424
})
2525
export class BookAuthorsComponent {
26-
@Input() book: Book | undefined = undefined;
26+
book = input<Book>();
2727

28-
get authors() {
29-
return this.book?.volumeInfo.authors || [];
30-
}
28+
protected readonly authors = computed(
29+
() => this.book()?.volumeInfo.authors || []
30+
);
3131
}

0 commit comments

Comments
 (0)