Skip to content

Commit

Permalink
feat: finalize migration to Signals
Browse files Browse the repository at this point in the history
  • Loading branch information
rainerhahnekamp committed Sep 29, 2024
1 parent 8a5d389 commit d6cc1c7
Show file tree
Hide file tree
Showing 23 changed files with 209 additions and 186 deletions.
8 changes: 6 additions & 2 deletions projects/example-app/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import { rootReducers, metaReducers } from '@example-app/reducers';

import { APP_ROUTES } from '@example-app/app.routing';
import { UserEffects, RouterEffects } from '@example-app/core/effects';
import { provideRouter, withHashLocation } from '@angular/router';
import {
provideRouter,
withComponentInputBinding,
withHashLocation,
} from '@angular/router';
import { AuthEffects } from './auth/effects';
import { provideAuth } from '@example-app/auth/reducers';
import { provideLayout } from '@example-app/core/reducers/layout.reducer';
Expand All @@ -20,7 +24,7 @@ export const appConfig: ApplicationConfig = {
providers: [
provideAnimationsAsync(),
provideHttpClient(),
provideRouter(APP_ROUTES, withHashLocation()),
provideRouter(APP_ROUTES, withHashLocation(), withComponentInputBinding()),

/**
* provideStore() is imported once in the root providers, accepting a reducer
Expand Down
6 changes: 2 additions & 4 deletions projects/example-app/src/app/app.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import { NotFoundPageComponent } from '@example-app/core/containers';
export const APP_ROUTES: Routes = [
{
path: 'login',
loadChildren: () =>
import('@example-app/auth/auth.routes').then((m) => m.AUTH_ROUTES),
loadChildren: () => import('@example-app/auth/auth.routes'),
},
{ path: '', redirectTo: '/books', pathMatch: 'full' },
{
path: 'books',
loadChildren: () =>
import('@example-app/books/books.routes').then((m) => m.BOOKS_ROUTES),
loadChildren: () => import('@example-app/books/books.routes'),
canActivate: [authGuard],
},
{
Expand Down
4 changes: 2 additions & 2 deletions projects/example-app/src/app/auth/auth.routes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Routes } from '@angular/router';
import { LoginPageComponent } from '@example-app/auth/containers';

export const AUTH_ROUTES: Routes = [
export default [
{
path: '',
component: LoginPageComponent,
data: { title: 'Login' },
},
];
] satisfies Routes;
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

exports[`Login Page should compile 1`] = `
<bc-login-form
errorMessage="null"
errorMessage={[Function Function]}
form={[Function FormGroup]}
submitted={[Function EventEmitter_]}
pending={[Function Function]}
pendingEffect={[Function EffectHandle]}
submitted={[Function OutputEmitterRef]}
>
<mat-card
class="mat-mdc-card mdc-card"
Expand Down Expand Up @@ -149,9 +151,11 @@ exports[`Login Page should compile 1`] = `

exports[`Login Page should disable the form if pending 1`] = `
<bc-login-form
errorMessage="null"
errorMessage={[Function Function]}
form={[Function FormGroup]}
submitted={[Function EventEmitter_]}
pending={[Function Function]}
pendingEffect={[Function EffectHandle]}
submitted={[Function OutputEmitterRef]}
>
<mat-card
class="mat-mdc-card mdc-card"
Expand All @@ -165,16 +169,19 @@ exports[`Login Page should disable the form if pending 1`] = `
class="mat-mdc-card-content"
>
<form
class="ng-untouched ng-pristine"
class="ng-untouched ng-pristine ng-valid"
novalidate=""
>
<p>
<mat-form-field
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"
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"
>
<div
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--disabled"
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label"
>
<div
class="mat-mdc-form-field-focus-overlay"
/>
<div
class="mat-mdc-form-field-flex"
>
Expand All @@ -184,7 +191,7 @@ exports[`Login Page should disable the form if pending 1`] = `
<input
aria-invalid="false"
aria-required="false"
class="mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine cdk-text-field-autofill-monitored"
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"
data-testid="username"
disabled=""
formcontrolname="username"
Expand Down Expand Up @@ -215,11 +222,14 @@ exports[`Login Page should disable the form if pending 1`] = `
</p>
<p>
<mat-form-field
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"
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"
>
<div
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--disabled"
class="mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label"
>
<div
class="mat-mdc-form-field-focus-overlay"
/>
<div
class="mat-mdc-form-field-flex"
>
Expand All @@ -229,7 +239,7 @@ exports[`Login Page should disable the form if pending 1`] = `
<input
aria-invalid="false"
aria-required="false"
class="mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine cdk-text-field-autofill-monitored"
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"
data-testid="password"
disabled=""
formcontrolname="password"
Expand Down Expand Up @@ -292,9 +302,11 @@ exports[`Login Page should disable the form if pending 1`] = `

exports[`Login Page should display an error message if provided 1`] = `
<bc-login-form
errorMessage={[Function String]}
errorMessage={[Function Function]}
form={[Function FormGroup]}
submitted={[Function EventEmitter_]}
pending={[Function Function]}
pendingEffect={[Function EffectHandle]}
submitted={[Function OutputEmitterRef]}
>
<mat-card
class="mat-mdc-card mdc-card"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('Login Page', () => {
});

fixture = TestBed.createComponent(LoginFormComponent);
fixture.componentRef.setInput('pending', false);
instance = fixture.componentInstance;
});

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

it('should disable the form if pending', () => {
instance.pending = true;
fixture.componentRef.setInput('pending', true);

fixture.detectChanges();

expect(fixture).toMatchSnapshot();
});

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

fixture.detectChanges();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import {
Component,
Input,
Output,
EventEmitter,
input,
untracked,
effect,
output,
} from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { Credentials } from '@example-app/auth/models';
import { MaterialModule } from '@example-app/material';
Expand Down Expand Up @@ -36,9 +45,9 @@ import { MaterialModule } from '@example-app/material';
</mat-form-field>
</p>
@if (errorMessage) {
@if (errorMessage()) {
<p class="login-error">
{{ errorMessage }}
{{ errorMessage() }}
</p>
}
Expand Down Expand Up @@ -87,18 +96,17 @@ import { MaterialModule } from '@example-app/material';
],
})
export class LoginFormComponent {
@Input()
set pending(isPending: boolean) {
if (isPending) {
this.form.disable();
} else {
this.form.enable();
}
}
readonly pending = input.required<boolean>();

private readonly pendingEffect = effect(() => {
const pending = this.pending();

untracked(() => (pending ? this.form.disable() : this.form.enable()));
});

@Input() errorMessage: string | null = null;
readonly errorMessage = input<string | null>(null);

@Output() submitted = new EventEmitter<Credentials>();
submitted = output<Credentials>();

protected readonly form: FormGroup = new FormGroup({
username: new FormControl('ngrx'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

exports[`Login Page should compile 1`] = `
<bc-login-page
error$={[Function _Store]}
pending$={[Function _Store]}
store={[Function _MockStore]}
error={[Function Function]}
pending={[Function Function]}
store={[Function MockStore]}
>
<bc-login-form>
<mat-card
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('Login Page', () => {
});

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

instance.onSubmit(credentials);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import { AsyncPipe } from '@angular/common';
template: `
<bc-login-form
(submitted)="onSubmit($event)"
[pending]="(pending$ | async)!"
[errorMessage]="error$ | async"
[pending]="pending()"
[errorMessage]="error()"
>
</bc-login-form>
`,
styles: [],
})
export class LoginPageComponent {
private store = inject(Store);
private readonly store = inject(Store);

protected readonly pending$ = this.store.select(
protected readonly pending = this.store.selectSignal(
fromAuth.selectLoginPagePending
);
protected readonly error$ = this.store.select(fromAuth.selectLoginPageError);
protected readonly error = this.store.selectSignal(
fromAuth.selectLoginPageError
);

onSubmit(credentials: Credentials) {
this.store.dispatch(LoginPageActions.login({ credentials }));
Expand Down
4 changes: 2 additions & 2 deletions projects/example-app/src/app/books/books.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { bookExistsGuard } from '@example-app/books/guards';
import { provideBooks } from '@example-app/books/reducers';

export const BOOKS_ROUTES: Routes = [
export default [
{
path: '',
providers: [provideBooks()],
Expand All @@ -31,4 +31,4 @@ export const BOOKS_ROUTES: Routes = [
},
],
},
];
] satisfies Routes;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, computed, input, Input } from '@angular/core';

import { Book } from '@example-app/books/models';
import { MaterialModule } from '@example-app/material';
Expand All @@ -11,7 +11,7 @@ import { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';
template: `
<h5 mat-subheader>Written By:</h5>
<span>
{{ authors | bcAddCommas }}
{{ authors() | bcAddCommas }}
</span>
`,
styles: [
Expand All @@ -23,9 +23,9 @@ import { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';
],
})
export class BookAuthorsComponent {
@Input() book: Book | undefined = undefined;
book = input<Book>();

get authors() {
return this.book?.volumeInfo.authors || [];
}
protected readonly authors = computed(
() => this.book()?.volumeInfo.authors || []
);
}
Loading

0 comments on commit d6cc1c7

Please sign in to comment.