Skip to content

Commit

Permalink
Merge pull request #311 from stefanoslig/refactor-forms-part-1
Browse files Browse the repository at this point in the history
refactor forms part 1
  • Loading branch information
stefanoslig authored Jun 16, 2024
2 parents 0198891 + e6b1d7d commit 0851b03
Show file tree
Hide file tree
Showing 18 changed files with 195 additions and 118 deletions.
19 changes: 9 additions & 10 deletions libs/auth/data-access/src/lib/auth.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { AuthState, authInitialState, initialUserValue } from './auth.model';
import { inject } from '@angular/core';
import { AuthService } from './services/auth.service';
import { exhaustMap, pipe, switchMap, tap } from 'rxjs';
import { concatLatestFrom, tapResponse } from '@ngrx/operators';
import { tapResponse } from '@ngrx/operators';
import { Store } from '@ngrx/store';
import { formsActions, ngrxFormsQuery } from '@realworld/core/forms';
import { formsActions } from '@realworld/core/forms';
import { LocalStorageJwtService } from './services/local-storage-jwt.service';
import { Router } from '@angular/router';
import { LoginUser, NewUser } from '@realworld/core/api-types';

export const AuthStore = signalStore(
{ providedIn: 'root' },
Expand All @@ -27,11 +28,10 @@ export const AuthStore = signalStore(
tap(({ user }) => patchState(store, { user, loggedIn: true })),
),
),
login: rxMethod<void>(
login: rxMethod<LoginUser>(
pipe(
concatLatestFrom(() => reduxStore.select(ngrxFormsQuery.selectData)),
exhaustMap(([, data]) =>
authService.login(data).pipe(
exhaustMap((credentials) =>
authService.login(credentials).pipe(
tapResponse({
next: ({ user }) => {
patchState(store, { user, loggedIn: true });
Expand All @@ -44,11 +44,10 @@ export const AuthStore = signalStore(
),
),
),
register: rxMethod<void>(
register: rxMethod<NewUser>(
pipe(
concatLatestFrom(() => reduxStore.select(ngrxFormsQuery.selectData)),
exhaustMap(([, data]) =>
authService.register(data).pipe(
exhaustMap((newUserData) =>
authService.register(newUserData).pipe(
tapResponse({
next: ({ user }) => {
patchState(store, { user, loggedIn: true });
Expand Down
Empty file.
35 changes: 30 additions & 5 deletions libs/auth/feature-auth/src/lib/login/login.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,36 @@ <h1 class="text-xs-center">Sign in</h1>

<cdt-list-errors></cdt-list-errors>

<cdt-dynamic-form (updateForm)="updateForm($event)" [data$]="data$" [structure$]="structure$">
</cdt-dynamic-form>
<button data-e2e-id="sign-in" (click)="submit()" class="btn btn-lg btn-primary pull-xs-right" type="submit">
Sign in
</button>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-group">
<input
formControlName="email"
type="email"
id="email"
placeholder="Email"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.email"></cdt-input-errors>
</div>
<div class="form-group">
<input
formControlName="password"
type="password"
id="password"
placeholder="Password"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.password"></cdt-input-errors>
</div>
<button
data-e2e-id="sign-in"
class="btn btn-lg btn-primary pull-xs-right"
[disabled]="form.invalid || form.pending"
type="submit"
>
Sign in
</button>
</form>
</div>
</div>
</div>
Expand Down
57 changes: 14 additions & 43 deletions libs/auth/feature-auth/src/lib/login/login.component.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,27 @@
import { DynamicFormComponent, Field, formsActions, ListErrorsComponent, ngrxFormsQuery } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core';
import { Validators } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { InputErrorsComponent, ListErrorsComponent } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { AuthStore } from '@realworld/auth/data-access';
import { Store } from '@ngrx/store';

const structure: Field[] = [
{
type: 'INPUT',
name: 'email',
placeholder: 'Email',
validator: [Validators.required],
},
{
type: 'INPUT',
name: 'password',
placeholder: 'Password',
validator: [Validators.required],
attrs: {
type: 'password',
},
},
];

@Component({
selector: 'cdt-login',
standalone: true,
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
imports: [ListErrorsComponent, DynamicFormComponent, RouterModule],
imports: [ListErrorsComponent, RouterLink, ReactiveFormsModule, InputErrorsComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoginComponent implements OnInit, OnDestroy {
private readonly store = inject(Store);
export class LoginComponent {
private readonly authStore = inject(AuthStore);
private readonly fb = inject(FormBuilder);

structure$ = this.store.select(ngrxFormsQuery.selectStructure);
data$ = this.store.select(ngrxFormsQuery.selectData);

ngOnInit() {
this.store.dispatch(formsActions.setStructure({ structure }));
}

updateForm(changes: any) {
this.store.dispatch(formsActions.updateData({ data: changes }));
}

submit() {
this.authStore.login();
}
form = this.fb.nonNullable.group({
email: ['', [Validators.required]],
password: ['', [Validators.required]],
});

ngOnDestroy() {
this.store.dispatch(formsActions.initializeForm());
onSubmit() {
this.authStore.login(this.form.getRawValue());
this.form.reset();
}
}
45 changes: 40 additions & 5 deletions libs/auth/feature-auth/src/lib/register/register.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,46 @@ <h1 class="text-xs-center">Sign in</h1>

<cdt-list-errors></cdt-list-errors>

<cdt-dynamic-form (updateForm)="updateForm($event)" [data$]="data$" [structure$]="structure$">
</cdt-dynamic-form>
<button data-e2e-id="sign-up" (click)="submit()" class="btn btn-lg btn-primary pull-xs-right" type="submit">
Sign up
</button>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-group">
<input
formControlName="username"
type="text"
id="username"
placeholder="Username"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.username" />
</div>
<div class="form-group">
<input
formControlName="email"
type="email"
id="email"
placeholder="Email"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.email" />
</div>
<div class="form-group">
<input
formControlName="password"
type="password"
id="password"
placeholder="Password"
class="form-control form-control-lg"
/>
<cdt-input-errors [control]="form.controls.password" />
</div>
<button
data-e2e-id="sign-up"
class="btn btn-lg btn-primary pull-xs-right"
[disabled]="form.invalid || form.pending"
type="submit"
>
Sign up
</button>
</form>
</div>
</div>
</div>
Expand Down
61 changes: 14 additions & 47 deletions libs/auth/feature-auth/src/lib/register/register.component.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,29 @@
import { DynamicFormComponent, Field, formsActions, ListErrorsComponent, ngrxFormsQuery } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core';
import { Validators } from '@angular/forms';
import { InputErrorsComponent, ListErrorsComponent } from '@realworld/core/forms';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AuthStore } from '@realworld/auth/data-access';
import { Store } from '@ngrx/store';

const structure: Field[] = [
{
type: 'INPUT',
name: 'username',
placeholder: 'Username',
validator: [Validators.required],
},
{
type: 'INPUT',
name: 'email',
placeholder: 'Email',
validator: [Validators.required],
},
{
type: 'INPUT',
name: 'password',
placeholder: 'Password',
validator: [Validators.required],
attrs: {
type: 'password',
},
},
];
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
selector: 'cdt-register',
standalone: true,
templateUrl: './register.component.html',
styleUrls: ['./register.component.css'],
imports: [ListErrorsComponent, DynamicFormComponent, RouterModule],
imports: [ListErrorsComponent, RouterModule, ReactiveFormsModule, InputErrorsComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RegisterComponent implements OnInit, OnDestroy {
private readonly store = inject(Store);
export class RegisterComponent {
private readonly authStore = inject(AuthStore);
private readonly fb = inject(FormBuilder);

structure$ = this.store.select(ngrxFormsQuery.selectStructure);
data$ = this.store.select(ngrxFormsQuery.selectData);

ngOnInit() {
this.store.dispatch(formsActions.setStructure({ structure }));
}

updateForm(changes: any) {
this.store.dispatch(formsActions.updateData({ data: changes }));
}

submit() {
this.authStore.register();
}
form = this.fb.nonNullable.group({
username: ['', [Validators.required]],
email: ['', [Validators.required]],
password: ['', [Validators.required]],
});

ngOnDestroy() {
this.store.dispatch(formsActions.initializeForm());
onSubmit() {
this.authStore.register(this.form.getRawValue());
this.form.reset();
}
}
1 change: 1 addition & 0 deletions libs/core/forms/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { ListErrorsComponent } from './lib/list-errors/list-errors.component';
export { DynamicFormComponent } from './lib/dynamic-form/dynamic-form.component';
export { InputErrorsComponent } from './lib/input-errors/input-errors.component';
export * from './lib/+state/forms.actions';
export * from './lib/+state/forms.reducer';
export * from './lib/+state/forms.selectors';
Expand Down
Empty file.
6 changes: 3 additions & 3 deletions libs/core/forms/src/lib/fields/input/input.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<fieldset [formGroup]="group" class="form-group">
<input
[formControlName]="field?.name"
[attr.placeholder]="field?.placeholder"
[formControlName]="field.name"
[attr.placeholder]="field.placeholder"
class="form-control form-control-lg"
[attr.type]="field?.attrs?.type || 'text'"
[attr.type]="field.attrs?.type || 'text'"
/>
</fieldset>
1 change: 0 additions & 1 deletion libs/core/forms/src/lib/fields/input/input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { FormGroup, ReactiveFormsModule } from '@angular/forms';
selector: 'cdt-input',
standalone: true,
templateUrl: './input.component.html',
styleUrls: ['./input.component.css'],
imports: [ReactiveFormsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<fieldset [formGroup]="group" class="form-group">
<textarea
class="form-control form-control-lg"
[attr.rows]="field?.attrs?.rows || 5"
[formControlName]="field?.name"
[attr.placeholder]="field?.placeholder"
[attr.rows]="field.attrs?.rows || 5"
[formControlName]="field.name"
[attr.placeholder]="field.placeholder"
>
</textarea>
</fieldset>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { FormGroup, ReactiveFormsModule } from '@angular/forms';
selector: 'cdt-textarea',
standalone: true,
templateUrl: './textarea.component.html',
styleUrls: ['./textarea.component.css'],
imports: [ReactiveFormsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
Expand Down
17 changes: 17 additions & 0 deletions libs/core/forms/src/lib/input-errors/error-mapper-pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { VALIDATION_ERROR_MESSAGES } from './error-messages';

@Pipe({
name: 'errorMapper',
standalone: true,
})
export class ErrorMapperPipe implements PipeTransform {
private errorMessages = inject(VALIDATION_ERROR_MESSAGES);

transform(key: string, errValue: any): string {
if (!this.errorMessages[key]) {
return '';
}
return this.errorMessages[key](errValue);
}
}
12 changes: 12 additions & 0 deletions libs/core/forms/src/lib/input-errors/error-messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { InjectionToken } from '@angular/core';

export const ERROR_MESSAGES: { [key in string]: (args?: any) => string } = {
required: () => `Required field`,
email: () => `Not a valid email`,
minlength: ({ requiredLength }) => `The length should be at least ${requiredLength} characters`,
};

export const VALIDATION_ERROR_MESSAGES = new InjectionToken(`Validation Messages`, {
providedIn: 'root',
factory: () => ERROR_MESSAGES,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div *isErrorVisible="control()">
<ul class="error-messages">
@for (error of control().errors | keyvalue; track error) {
<li data-e2e-id="error">
{{ error.key | errorMapper: error.value }}
</li>
}
</ul>
</div>
16 changes: 16 additions & 0 deletions libs/core/forms/src/lib/input-errors/input-errors.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { KeyValuePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { ErrorMapperPipe } from './error-mapper-pipe';
import { IsErrorVisibleDirective } from './is-error-visible.directive';

@Component({
selector: 'cdt-input-errors',
standalone: true,
templateUrl: './input-errors.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [KeyValuePipe, ErrorMapperPipe, IsErrorVisibleDirective],
})
export class InputErrorsComponent {
readonly control = input.required<AbstractControl>();
}
Loading

0 comments on commit 0851b03

Please sign in to comment.