Skip to content

Commit 489e0de

Browse files
feat(module:checkbox): redesign the checkbox group component (#8932)
1 parent c76433d commit 489e0de

17 files changed

+493
-387
lines changed

components/checkbox/checkbox-group.component.ts

+104-75
Original file line numberDiff line numberDiff line change
@@ -4,114 +4,115 @@
44
*/
55

66
import { FocusMonitor } from '@angular/cdk/a11y';
7-
import { Direction, Directionality } from '@angular/cdk/bidi';
7+
import { Directionality } from '@angular/cdk/bidi';
88
import {
9-
ChangeDetectorRef,
9+
ChangeDetectionStrategy,
1010
Component,
11+
DestroyRef,
1112
ElementRef,
12-
Input,
13-
OnDestroy,
14-
OnInit,
1513
ViewEncapsulation,
14+
afterNextRender,
1615
booleanAttribute,
17-
forwardRef
16+
computed,
17+
forwardRef,
18+
inject,
19+
input,
20+
linkedSignal,
21+
signal
1822
} from '@angular/core';
23+
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
1924
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
20-
import { Subject } from 'rxjs';
21-
import { takeUntil } from 'rxjs/operators';
2225

2326
import { OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types';
2427

2528
import { NzCheckboxComponent } from './checkbox.component';
29+
import { NZ_CHECKBOX_GROUP } from './tokens';
2630

27-
export interface NzCheckBoxOptionInterface {
31+
export interface NzCheckboxOption {
2832
label: string;
29-
value: string;
30-
checked?: boolean;
33+
value: string | number;
3134
disabled?: boolean;
3235
}
3336

37+
/**
38+
* @deprecated Deprecated in v19.0.0. Please use {@link NzCheckboxOption} to instead.
39+
*/
40+
export type NzCheckBoxOptionInterface = NzCheckboxOption;
41+
3442
@Component({
3543
selector: 'nz-checkbox-group',
3644
exportAs: 'nzCheckboxGroup',
37-
preserveWhitespaces: false,
38-
encapsulation: ViewEncapsulation.None,
45+
imports: [NzCheckboxComponent],
3946
template: `
40-
@for (option of options; track option.value) {
41-
<label
42-
nz-checkbox
43-
class="ant-checkbox-group-item"
44-
[nzDisabled]="option.disabled || nzDisabled"
45-
[nzChecked]="option.checked!"
46-
(nzCheckedChange)="onCheckedChange(option, $event)"
47-
>
48-
<span>{{ option.label }}</span>
49-
</label>
50-
}
47+
<ng-content>
48+
@for (option of normalizedOptions(); track option.value) {
49+
<label
50+
nz-checkbox
51+
[nzValue]="option.value"
52+
[nzName]="nzName()"
53+
[nzDisabled]="option.disabled || finalDisabled()"
54+
>
55+
{{ option.label }}
56+
</label>
57+
}
58+
</ng-content>
5159
`,
5260
providers: [
5361
{
5462
provide: NG_VALUE_ACCESSOR,
5563
useExisting: forwardRef(() => NzCheckboxGroupComponent),
5664
multi: true
65+
},
66+
{
67+
provide: NZ_CHECKBOX_GROUP,
68+
useExisting: forwardRef(() => NzCheckboxGroupComponent)
5769
}
5870
],
5971
host: {
6072
class: 'ant-checkbox-group',
61-
'[class.ant-checkbox-group-rtl]': `dir === 'rtl'`
73+
'[class.ant-checkbox-group-rtl]': `dir() === 'rtl'`
6274
},
63-
imports: [NzCheckboxComponent]
75+
encapsulation: ViewEncapsulation.None,
76+
changeDetection: ChangeDetectionStrategy.OnPush
6477
})
65-
export class NzCheckboxGroupComponent implements ControlValueAccessor, OnInit, OnDestroy {
66-
onChange: OnChangeType = () => {};
67-
onTouched: OnTouchedType = () => {};
68-
options: NzCheckBoxOptionInterface[] = [];
69-
@Input({ transform: booleanAttribute }) nzDisabled = false;
70-
71-
dir: Direction = 'ltr';
72-
73-
private destroy$ = new Subject<void>();
74-
private isNzDisableFirstChange: boolean = true;
75-
76-
onCheckedChange(option: NzCheckBoxOptionInterface, checked: boolean): void {
77-
option.checked = checked;
78-
this.onChange(this.options);
79-
}
80-
81-
constructor(
82-
private elementRef: ElementRef,
83-
private focusMonitor: FocusMonitor,
84-
private cdr: ChangeDetectorRef,
85-
private directionality: Directionality
86-
) {}
87-
88-
ngOnInit(): void {
89-
this.focusMonitor
90-
.monitor(this.elementRef, true)
91-
.pipe(takeUntil(this.destroy$))
92-
.subscribe(focusOrigin => {
93-
if (!focusOrigin) {
94-
Promise.resolve().then(() => this.onTouched());
95-
}
78+
export class NzCheckboxGroupComponent implements ControlValueAccessor {
79+
private onChange: OnChangeType = () => {};
80+
private onTouched: OnTouchedType = () => {};
81+
private isDisabledFirstChange = true;
82+
private readonly directionality = inject(Directionality);
83+
84+
readonly nzName = input<string | null>(null);
85+
readonly nzDisabled = input(false, { transform: booleanAttribute });
86+
readonly nzOptions = input<NzCheckboxOption[]>([]);
87+
readonly value = signal<Array<NzCheckboxOption['value']> | null>(null);
88+
readonly finalDisabled = linkedSignal(() => this.nzDisabled());
89+
90+
protected readonly dir = toSignal(this.directionality.change, { initialValue: this.directionality.value });
91+
protected readonly normalizedOptions = computed(() => normalizeOptions(this.nzOptions()));
92+
93+
constructor() {
94+
const elementRef = inject(ElementRef);
95+
const focusMonitor = inject(FocusMonitor);
96+
const destroyRef = inject(DestroyRef);
97+
98+
afterNextRender(() => {
99+
focusMonitor
100+
.monitor(elementRef, true)
101+
.pipe(takeUntilDestroyed(destroyRef))
102+
.subscribe(focusOrigin => {
103+
if (!focusOrigin) {
104+
this.onTouched();
105+
}
106+
});
107+
108+
destroyRef.onDestroy(() => {
109+
focusMonitor.stopMonitoring(elementRef);
96110
});
97-
98-
this.directionality.change?.pipe(takeUntil(this.destroy$)).subscribe((direction: Direction) => {
99-
this.dir = direction;
100-
this.cdr.detectChanges();
101111
});
102-
103-
this.dir = this.directionality.value;
104-
}
105-
106-
ngOnDestroy(): void {
107-
this.focusMonitor.stopMonitoring(this.elementRef);
108-
this.destroy$.next();
109-
this.destroy$.complete();
110112
}
111113

112-
writeValue(value: NzCheckBoxOptionInterface[]): void {
113-
this.options = value;
114-
this.cdr.markForCheck();
114+
writeValue(value: Array<string | number> | null): void {
115+
this.value.set(value);
115116
}
116117

117118
registerOnChange(fn: OnChangeType): void {
@@ -123,8 +124,36 @@ export class NzCheckboxGroupComponent implements ControlValueAccessor, OnInit, O
123124
}
124125

125126
setDisabledState(disabled: boolean): void {
126-
this.nzDisabled = (this.isNzDisableFirstChange && this.nzDisabled) || disabled;
127-
this.isNzDisableFirstChange = false;
128-
this.cdr.markForCheck();
127+
if (!this.isDisabledFirstChange) {
128+
this.finalDisabled.set(disabled);
129+
}
130+
this.isDisabledFirstChange = false;
129131
}
132+
133+
onCheckedChange(optionValue: NzCheckboxOption['value'], checked: boolean): void {
134+
if (this.finalDisabled()) return;
135+
136+
this.value.update(value => {
137+
if (checked) {
138+
return value?.concat(optionValue) || [optionValue];
139+
} else {
140+
return value?.filter(val => val !== optionValue) || [];
141+
}
142+
});
143+
144+
this.onChange(this.value());
145+
}
146+
}
147+
148+
function normalizeOptions(value: string[] | number[] | NzCheckboxOption[]): NzCheckboxOption[] {
149+
return value.map(item => {
150+
if (typeof item === 'string' || typeof item === 'number') {
151+
return {
152+
label: `${item}`,
153+
value: item
154+
};
155+
}
156+
157+
return item;
158+
});
130159
}

0 commit comments

Comments
 (0)