From 1143acb33615354c55c6613d114d1b6db72a1978 Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Tue, 15 Feb 2022 12:48:05 +0530 Subject: [PATCH 1/9] feat(checkbox): add control value accessor implementation --- .../src/checkbox/checkbox.component.ts | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/projects/components/src/checkbox/checkbox.component.ts b/projects/components/src/checkbox/checkbox.component.ts index e47bced19..fad5fb896 100644 --- a/projects/components/src/checkbox/checkbox.component.ts +++ b/projects/components/src/checkbox/checkbox.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatCheckboxChange } from '@angular/material/checkbox'; @Component({ @@ -8,31 +9,66 @@ import { MatCheckboxChange } from '@angular/material/checkbox'; template: ` - ` + `, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: CheckboxComponent, + multi: true + } + ] }) -export class CheckboxComponent { +export class CheckboxComponent implements ControlValueAccessor { @Input() public label?: string; @Input() - public checked: boolean | undefined; + public set checked(checked: boolean | undefined) { + this.isChecked = checked ?? false; + } @Input() - public disabled: boolean | undefined; + public set disabled(disabled: boolean | undefined) { + this.isDisabled = disabled ?? false; + } @Output() public readonly checkedChange: EventEmitter = new EventEmitter(); + public isChecked: boolean = false; + public isDisabled: boolean = false; + + private onTouched: () => void = () => {}; + private onChanged: (value: boolean) => void = () => {}; + public onCheckboxChange(event: MatCheckboxChange): void { - this.checked = event.checked; - this.checkedChange.emit(this.checked); + this.isChecked = event.checked; + this.checkedChange.emit(this.isChecked); + this.onChanged(this.isChecked); + this.onTouched(); + } + + public registerOnChange(fn: any): void { + this.onChanged = fn; + } + + public registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + } + + public writeValue(isChecked: boolean): void { + this.isChecked = isChecked; } } From 9f3b630024b0b8ca96d642fed3579401ea9cf76a Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Tue, 15 Feb 2022 12:52:19 +0530 Subject: [PATCH 2/9] test(checkbox): add supporting test cases for control value accessor --- .../src/checkbox/checkbox.component.test.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/projects/components/src/checkbox/checkbox.component.test.ts b/projects/components/src/checkbox/checkbox.component.test.ts index 20850401c..c10523773 100644 --- a/projects/components/src/checkbox/checkbox.component.test.ts +++ b/projects/components/src/checkbox/checkbox.component.test.ts @@ -1,4 +1,5 @@ import { fakeAsync } from '@angular/core/testing'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { createHostFactory, Spectator } from '@ngneat/spectator/jest'; import { CheckboxComponent } from './checkbox.component'; @@ -9,7 +10,7 @@ describe('Checkbox component', () => { const createHost = createHostFactory({ component: CheckboxComponent, - imports: [TraceCheckboxModule, RouterTestingModule], + imports: [TraceCheckboxModule, RouterTestingModule, ReactiveFormsModule], providers: [], declareComponent: false }); @@ -52,12 +53,32 @@ describe('Checkbox component', () => { // Click will toggle the values to true spectator.click(inputElement); - expect(spectator.component.checked).toBe(true); + expect(spectator.component.isChecked).toBe(true); expect(checkboxChangeSpy).toHaveBeenCalledWith(true); // Click will toggle the values to false spectator.click(inputElement); - expect(spectator.component.checked).toBe(false); + expect(spectator.component.isChecked).toBe(false); expect(checkboxChangeSpy).toHaveBeenCalledWith(false); })); + + test('should work correctly with control value accessor', () => { + const formControl = new FormControl(false); + spectator = createHost( + ` + `, + { + hostProps: { + formControl: formControl + } + } + ); + expect(spectator.component.isChecked).toBe(false); + + formControl.setValue(true); + expect(spectator.component.isChecked).toBe(true); + + formControl.disable(); + expect(spectator.component.isDisabled).toBe(true); + }); }); From 0cc270ce30450823c669f23b71e1f614626b011c Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Tue, 15 Feb 2022 13:55:54 +0530 Subject: [PATCH 3/9] feat(checkbox): fix lint issue --- projects/components/src/checkbox/checkbox.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/components/src/checkbox/checkbox.component.ts b/projects/components/src/checkbox/checkbox.component.ts index fad5fb896..d29d11288 100644 --- a/projects/components/src/checkbox/checkbox.component.ts +++ b/projects/components/src/checkbox/checkbox.component.ts @@ -46,8 +46,8 @@ export class CheckboxComponent implements ControlValueAccessor { public isChecked: boolean = false; public isDisabled: boolean = false; - private onTouched: () => void = () => {}; - private onChanged: (value: boolean) => void = () => {}; + private onTouched!: () => void; + private onChanged!: (value: boolean) => void; public onCheckboxChange(event: MatCheckboxChange): void { this.isChecked = event.checked; @@ -56,11 +56,11 @@ export class CheckboxComponent implements ControlValueAccessor { this.onTouched(); } - public registerOnChange(fn: any): void { + public registerOnChange(fn: (value: boolean) => void): void { this.onChanged = fn; } - public registerOnTouched(fn: any): void { + public registerOnTouched(fn: () => void): void { this.onTouched = fn; } From 24a94c1570f6ec15cc729b49495ef9f8b5bbbfb0 Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Thu, 17 Feb 2022 15:38:13 +0530 Subject: [PATCH 4/9] fix(checkbox): type coercion fix --- projects/components/src/checkbox/checkbox.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/components/src/checkbox/checkbox.component.ts b/projects/components/src/checkbox/checkbox.component.ts index d29d11288..ba8313817 100644 --- a/projects/components/src/checkbox/checkbox.component.ts +++ b/projects/components/src/checkbox/checkbox.component.ts @@ -68,7 +68,7 @@ export class CheckboxComponent implements ControlValueAccessor { this.isDisabled = isDisabled; } - public writeValue(isChecked: boolean): void { - this.isChecked = isChecked; + public writeValue(isChecked: boolean | undefined): void { + this.isChecked = isChecked ?? false; } } From b5d2785bebd64bad1aed7e7981285b84cc684163 Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Fri, 18 Feb 2022 20:57:56 +0530 Subject: [PATCH 5/9] feat(toggle): add control value accessor --- .../toggle-switch.component.test.ts | 20 ++++++- .../toggle-switch/toggle-switch.component.ts | 53 ++++++++++++++++--- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/projects/components/src/toggle-switch/toggle-switch.component.test.ts b/projects/components/src/toggle-switch/toggle-switch.component.test.ts index 563ccabfa..1547e4ade 100644 --- a/projects/components/src/toggle-switch/toggle-switch.component.test.ts +++ b/projects/components/src/toggle-switch/toggle-switch.component.test.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; import { fakeAsync } from '@angular/core/testing'; -import { FormsModule } from '@angular/forms'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle'; import { createHostFactory, Spectator } from '@ngneat/spectator/jest'; import { ToggleSwitchSize } from './toggle-switch-size'; @@ -12,7 +12,7 @@ describe('Toggle Switch Component', () => { const createHost = createHostFactory({ component: ToggleSwitchComponent, shallow: true, - imports: [MatSlideToggleModule, FormsModule, CommonModule] + imports: [MatSlideToggleModule, FormsModule, CommonModule, ReactiveFormsModule] }); test('should pass properties to Mat Slide toggle correctly', fakeAsync(() => { @@ -41,4 +41,20 @@ describe('Toggle Switch Component', () => { spectator.triggerEventHandler(MatSlideToggle, 'change', new MatSlideToggleChange(matToggleComponent!, false)); expect(onCheckedChangeSpy).toHaveBeenCalledWith(false); })); + + test('should work correctly with control value accessor', () => { + const formControl = new FormControl(false); + spectator = createHost(``, { + hostProps: { + formControl: formControl + } + }); + expect(spectator.component.isChecked).toBe(false); + + formControl.setValue(true); + expect(spectator.component.isChecked).toBe(true); + + formControl.disable(); + expect(spectator.component.isDisabled).toBe(true); + }); }); diff --git a/projects/components/src/toggle-switch/toggle-switch.component.ts b/projects/components/src/toggle-switch/toggle-switch.component.ts index aa897013d..45308bef9 100644 --- a/projects/components/src/toggle-switch/toggle-switch.component.ts +++ b/projects/components/src/toggle-switch/toggle-switch.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { ToggleSwitchSize } from './toggle-switch-size'; @@ -10,25 +11,36 @@ import { ToggleSwitchSize } from './toggle-switch-size';
{{ this.label }}
- ` + `, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: ToggleSwitchComponent, + multi: true + } + ] }) -export class ToggleSwitchComponent { +export class ToggleSwitchComponent implements ControlValueAccessor { @Input() - public checked?: boolean; + public label?: string = ''; @Input() - public label?: string = ''; + public set checked(checked: boolean | undefined) { + this.isChecked = checked ?? false; + } @Input() - public disabled?: boolean; + public set disabled(disabled: boolean | undefined) { + this.isDisabled = disabled ?? false; + } @Input() public size: ToggleSwitchSize = ToggleSwitchSize.Small; @@ -36,7 +48,32 @@ export class ToggleSwitchComponent { @Output() public readonly checkedChange: EventEmitter = new EventEmitter(); + public isChecked: boolean = false; + public isDisabled: boolean = false; + + private onTouched!: () => void; + private onChanged!: (value: boolean) => void; + public onToggle(toggleChange: MatSlideToggleChange): void { - this.checkedChange.emit(toggleChange.checked); + this.isChecked = toggleChange.checked; + this.checkedChange.emit(this.isChecked); + this.onChanged(this.isChecked); + this.onTouched(); + } + + public registerOnChange(fn: (value: boolean) => void): void { + this.onChanged = fn; + } + + public registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + } + + public writeValue(isChecked: boolean | undefined): void { + this.isChecked = isChecked ?? false; } } From ba80ab8489bbab38d4756a8fdb344cd4108b3809 Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Fri, 18 Feb 2022 21:00:00 +0530 Subject: [PATCH 6/9] feat(toggle): add change detection --- .../components/src/toggle-switch/toggle-switch.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/components/src/toggle-switch/toggle-switch.component.ts b/projects/components/src/toggle-switch/toggle-switch.component.ts index 45308bef9..346465aa2 100644 --- a/projects/components/src/toggle-switch/toggle-switch.component.ts +++ b/projects/components/src/toggle-switch/toggle-switch.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { ToggleSwitchSize } from './toggle-switch-size'; @@ -54,6 +54,8 @@ export class ToggleSwitchComponent implements ControlValueAccessor { private onTouched!: () => void; private onChanged!: (value: boolean) => void; + public constructor(private readonly cdr: ChangeDetectorRef) {} + public onToggle(toggleChange: MatSlideToggleChange): void { this.isChecked = toggleChange.checked; this.checkedChange.emit(this.isChecked); @@ -71,9 +73,11 @@ export class ToggleSwitchComponent implements ControlValueAccessor { public setDisabledState(isDisabled: boolean): void { this.isDisabled = isDisabled; + this.cdr.markForCheck(); } public writeValue(isChecked: boolean | undefined): void { this.isChecked = isChecked ?? false; + this.cdr.markForCheck(); } } From 2e4537a023a01cdb6d4dcc0bd1fb70a87477373b Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Mon, 21 Feb 2022 12:27:03 +0530 Subject: [PATCH 7/9] fix(toggle): add checked getter to not make breaking change --- .../components/src/toggle-switch/toggle-switch.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/components/src/toggle-switch/toggle-switch.component.ts b/projects/components/src/toggle-switch/toggle-switch.component.ts index 346465aa2..682e05b1c 100644 --- a/projects/components/src/toggle-switch/toggle-switch.component.ts +++ b/projects/components/src/toggle-switch/toggle-switch.component.ts @@ -48,6 +48,10 @@ export class ToggleSwitchComponent implements ControlValueAccessor { @Output() public readonly checkedChange: EventEmitter = new EventEmitter(); + public get checked() { + return this.isChecked; + } + public isChecked: boolean = false; public isDisabled: boolean = false; From fa665d8a03fc25a22f4f2d048a8641f46ab210d8 Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Mon, 21 Feb 2022 12:29:35 +0530 Subject: [PATCH 8/9] fix(toggle): add getter for disabled --- .../src/toggle-switch/toggle-switch.component.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/projects/components/src/toggle-switch/toggle-switch.component.ts b/projects/components/src/toggle-switch/toggle-switch.component.ts index 682e05b1c..9075223e0 100644 --- a/projects/components/src/toggle-switch/toggle-switch.component.ts +++ b/projects/components/src/toggle-switch/toggle-switch.component.ts @@ -12,7 +12,7 @@ import { ToggleSwitchSize } from './toggle-switch-size'; @@ -48,10 +48,14 @@ export class ToggleSwitchComponent implements ControlValueAccessor { @Output() public readonly checkedChange: EventEmitter = new EventEmitter(); - public get checked() { + public get checked(): boolean { return this.isChecked; } + public get disabled(): boolean { + return this.isDisabled; + } + public isChecked: boolean = false; public isDisabled: boolean = false; From 7357fedfdfafc20a8044eff87f9512116a706f0d Mon Sep 17 00:00:00 2001 From: Adithya Sreyaj Date: Mon, 21 Feb 2022 19:22:38 +0530 Subject: [PATCH 9/9] fix(toggle): fix lint issue --- .../toggle-switch/toggle-switch.component.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/projects/components/src/toggle-switch/toggle-switch.component.ts b/projects/components/src/toggle-switch/toggle-switch.component.ts index 9075223e0..9a3598bbf 100644 --- a/projects/components/src/toggle-switch/toggle-switch.component.ts +++ b/projects/components/src/toggle-switch/toggle-switch.component.ts @@ -32,30 +32,30 @@ export class ToggleSwitchComponent implements ControlValueAccessor { @Input() public label?: string = ''; + @Input() + public size: ToggleSwitchSize = ToggleSwitchSize.Small; + @Input() public set checked(checked: boolean | undefined) { this.isChecked = checked ?? false; } - @Input() - public set disabled(disabled: boolean | undefined) { - this.isDisabled = disabled ?? false; + public get checked(): boolean { + return this.isChecked; } @Input() - public size: ToggleSwitchSize = ToggleSwitchSize.Small; - - @Output() - public readonly checkedChange: EventEmitter = new EventEmitter(); - - public get checked(): boolean { - return this.isChecked; + public set disabled(disabled: boolean | undefined) { + this.isDisabled = disabled ?? false; } public get disabled(): boolean { return this.isDisabled; } + @Output() + public readonly checkedChange: EventEmitter = new EventEmitter(); + public isChecked: boolean = false; public isDisabled: boolean = false;