Skip to content

Commit

Permalink
feat: Use Bootstrap styling to highlight form validation errors
Browse files Browse the repository at this point in the history
* Created directive to automatically add bootstrap class to input if invalid
* Updated various components with forms to use new directive
  • Loading branch information
jrassa committed Aug 22, 2024
1 parent 83a5c5b commit 5f4aa79
Show file tree
Hide file tree
Showing 14 changed files with 95 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { IsModelInvalidDirective } from '../../forms/is-model-invalid.directive';
import { ModalComponent } from '../../modal/modal/modal.component';
import { DialogAction, DialogReturn } from '../dialog.model';

Expand Down Expand Up @@ -33,7 +34,13 @@ export type ConfigurableDialogReturn = DialogReturn<Record<string, string>>;

@Component({
standalone: true,
imports: [CommonModule, FormsModule, ReactiveFormsModule, ModalComponent],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
ModalComponent,
IsModelInvalidDirective
],
templateUrl: './configurable-dialog.component.html',
styleUrls: ['./configurable-dialog.component.scss']
})
Expand Down
32 changes: 32 additions & 0 deletions src/app/common/forms/is-model-invalid.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Directive, HostListener, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NgModel } from '@angular/forms';

import { hostBinding } from 'ngxtension/host-binding';

@Directive({
selector: '[ngModel]',
standalone: true
})
export class IsModelInvalidDirective {
readonly #ngModel = inject(NgModel);

readonly isInvalid = hostBinding('class.is-invalid', signal(false));

constructor() {
this.#ngModel.control.statusChanges.pipe(takeUntilDestroyed()).subscribe(() => {
this.#updateIsInvalid();
});
}

@HostListener('focusout')
onFocusOut(): void {
this.#updateIsInvalid();
}

#updateIsInvalid() {
this.isInvalid.set(
(this.#ngModel.invalid && (this.#ngModel.dirty || this.#ngModel.touched)) ?? false
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ import { Router, RouterLink } from '@angular/router';

import { SkipToDirective } from '../../../../common';
import { DialogService } from '../../../../common/dialog';
import { IsModelInvalidDirective } from '../../../../common/forms/is-model-invalid.directive';
import { SystemAlertComponent, SystemAlertService } from '../../../../common/system-alert';
import { EndUserAgreement } from '../../../auth';
import { EuaService } from '../eua.service';

@Component({
standalone: true,
templateUrl: './manage-eua.component.html',
imports: [RouterLink, SystemAlertComponent, FormsModule, TitleCasePipe, SkipToDirective]
imports: [
RouterLink,
SystemAlertComponent,
FormsModule,
TitleCasePipe,
SkipToDirective,
IsModelInvalidDirective
]
})
export class ManageEuaComponent {
readonly #router = inject(Router);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NgSelectModule } from '@ng-select/ng-select';

import { SkipToDirective } from '../../../../common';
import { DialogService } from '../../../../common/dialog';
import { IsModelInvalidDirective } from '../../../../common/forms/is-model-invalid.directive';
import { SystemAlertComponent, SystemAlertService } from '../../../../common/system-alert';
import { Message, MessageService, MessageType } from '../../../messages';

Expand All @@ -21,7 +22,8 @@ import { Message, MessageService, MessageType } from '../../../messages';
FormsModule,
NgSelectModule,
TitleCasePipe,
SkipToDirective
SkipToDirective,
IsModelInvalidDirective
]
})
export class ManageMessageComponent {
Expand Down
4 changes: 3 additions & 1 deletion src/app/core/admin/user/manage-user/manage-user.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Router, RouterLink } from '@angular/router';
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';

import { SkipToDirective } from '../../../../common';
import { IsModelInvalidDirective } from '../../../../common/forms/is-model-invalid.directive';
import { JoinPipe } from '../../../../common/pipes';
import { SystemAlertComponent, SystemAlertService } from '../../../../common/system-alert';
import { EditUser, Role } from '../../../auth';
Expand All @@ -23,7 +24,8 @@ import { AdminUsersService } from '../admin-users.service';
FormsModule,
SkipToDirective,
JoinPipe,
NgbTooltip
NgbTooltip,
IsModelInvalidDirective
],
templateUrl: './manage-user.component.html',
styleUrls: ['./manage-user.component.scss']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ <h3 class="pt-2">What happened?</h3>
<textarea
class="form-control"
name="text"
cdkTextareaAutosize
placeholder="Enter Feedback"
required
style="height: 8rem"
[(ngModel)]="feedback.body"
>
</textarea>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TextFieldModule } from '@angular/cdk/text-field';
import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Expand All @@ -15,6 +16,7 @@ import { Router } from '@angular/router';
import { NgSelectModule } from '@ng-select/ng-select';

import { FlyoutComponent } from '../../../common';
import { IsModelInvalidDirective } from '../../../common/forms/is-model-invalid.directive';
import { APP_CONFIG } from '../../tokens';
import { Feedback } from '../feedback.model';
import { FeedbackService } from '../feedback.service';
Expand All @@ -24,7 +26,14 @@ import { FeedbackService } from '../feedback.service';
templateUrl: './feedback-flyout.component.html',
styleUrls: ['./feedback-flyout.component.scss'],
standalone: true,
imports: [FlyoutComponent, FormsModule, NgTemplateOutlet, NgSelectModule],
imports: [
FlyoutComponent,
FormsModule,
NgTemplateOutlet,
NgSelectModule,
TextFieldModule,
IsModelInvalidDirective
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FeedbackFlyoutComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ <h3 class="pt-2">What happened?</h3>
<textarea
class="form-control"
name="feedbackText"
cdkTextareaAutosize
placeholder="Enter Feedback"
required
[(ngModel)]="feedback.body"
Expand Down
11 changes: 10 additions & 1 deletion src/app/core/feedback/feedback-modal/feedback-modal.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DialogRef } from '@angular/cdk/dialog';
import { TextFieldModule } from '@angular/cdk/text-field';
import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Expand All @@ -15,6 +16,7 @@ import { Router } from '@angular/router';
import { NgSelectModule } from '@ng-select/ng-select';

import { ModalComponent } from '../../../common';
import { IsModelInvalidDirective } from '../../../common/forms/is-model-invalid.directive';
import { APP_CONFIG } from '../../tokens';
import { Feedback } from '../feedback.model';
import { FeedbackService } from '../feedback.service';
Expand All @@ -23,7 +25,14 @@ import { FeedbackService } from '../feedback.service';
templateUrl: './feedback-modal.component.html',
styleUrls: ['./feedback-modal.component.scss'],
standalone: true,
imports: [ModalComponent, FormsModule, NgTemplateOutlet, NgSelectModule],
imports: [
ModalComponent,
FormsModule,
NgTemplateOutlet,
NgSelectModule,
IsModelInvalidDirective,
TextFieldModule
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FeedbackModalComponent {
Expand Down
3 changes: 2 additions & 1 deletion src/app/core/signin/signin.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';

import { LoadingSpinnerComponent } from '../../common';
import { IsModelInvalidDirective } from '../../common/forms/is-model-invalid.directive';
import { SessionService } from '../auth';
import { NavigationService } from '../navigation.service';
import { APP_CONFIG } from '../tokens';
Expand All @@ -20,7 +21,7 @@ import { APP_CONFIG } from '../tokens';
templateUrl: './signin.component.html',
styleUrls: ['./signin.component.scss'],
standalone: true,
imports: [LoadingSpinnerComponent, FormsModule, RouterLink],
imports: [LoadingSpinnerComponent, FormsModule, RouterLink, IsModelInvalidDirective],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SigninComponent {
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/signup/signup.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ <h1>New Account Request</h1>
type="password"
autocomplete="off"
minlength="6"
required
[(ngModel)]="user.password"
/>
</div>
Expand All @@ -87,6 +88,7 @@ <h1>New Account Request</h1>
type="password"
autocomplete="off"
minlength="6"
required
[(ngModel)]="user.verifyPassword"
/>
</div>
Expand Down
9 changes: 8 additions & 1 deletion src/app/core/signup/signup.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';

import { IsModelInvalidDirective } from '../../common/forms/is-model-invalid.directive';
import { SystemAlertComponent, SystemAlertService } from '../../common/system-alert';
import { AuthenticationService, EditUser } from '../auth';

@Component({
standalone: true,
templateUrl: './signup.component.html',
imports: [RouterLink, SystemAlertComponent, FormsModule, TitleCasePipe],
imports: [
RouterLink,
SystemAlertComponent,
FormsModule,
TitleCasePipe,
IsModelInvalidDirective
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SignupComponent {
Expand Down
4 changes: 3 additions & 1 deletion src/app/core/teams/create-team/create-team.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, map, switchMap, tap } from 'rxjs/operators';

import { MultiSelectDirective, PagingOptions, SkipToDirective } from '../../../common';
import { IsModelInvalidDirective } from '../../../common/forms/is-model-invalid.directive';
import { SystemAlertComponent, SystemAlertService } from '../../../common/system-alert';
import { SessionService, User, UserExternalRolesSelectDirective } from '../../auth';
import { APP_CONFIG, APP_SESSION } from '../../tokens';
Expand All @@ -29,7 +30,8 @@ import { TeamsService } from '../teams.service';
UserExternalRolesSelectDirective,
TeamSelectDirective,
SkipToDirective,
TitleCasePipe
TitleCasePipe,
IsModelInvalidDirective
]
})
export class CreateTeamComponent implements OnInit {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { NgSelectModule } from '@ng-select/ng-select';
import { switchMap, tap } from 'rxjs/operators';

import { MultiSelectDirective } from '../../../../common';
import { IsModelInvalidDirective } from '../../../../common/forms/is-model-invalid.directive';
import { JoinPipe, UtcDatePipe } from '../../../../common/pipes';
import { SystemAlertService } from '../../../../common/system-alert';
import { SessionService, UserExternalRolesSelectDirective } from '../../../auth';
Expand Down Expand Up @@ -43,7 +44,8 @@ import { TeamsService } from '../../teams.service';
UtcDatePipe,
MultiSelectDirective,
NgSelectModule,
UserExternalRolesSelectDirective
UserExternalRolesSelectDirective,
IsModelInvalidDirective
],
changeDetection: ChangeDetectionStrategy.OnPush
})
Expand Down

0 comments on commit 5f4aa79

Please sign in to comment.