Skip to content

Commit 2704237

Browse files
authored
fix(module:radio): emit false to the ngModel whenever the radio button is deselected (#7270)
1 parent e95d941 commit 2704237

File tree

2 files changed

+75
-6
lines changed

2 files changed

+75
-6
lines changed

components/radio/radio.component.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Component,
1313
ElementRef,
1414
forwardRef,
15+
Inject,
1516
Input,
1617
NgZone,
1718
OnDestroy,
@@ -88,7 +89,7 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On
8889
isRadioButton = !!this.nzRadioButtonDirective;
8990
onChange: OnChangeType = () => {};
9091
onTouched: OnTouchedType = () => {};
91-
@ViewChild('inputElement', { static: false }) inputElement?: ElementRef;
92+
@ViewChild('inputElement', { static: true }) inputElement!: ElementRef<HTMLInputElement>;
9293
@Input() nzValue: NzSafeAny | null = null;
9394
@Input() @InputBoolean() nzDisabled = false;
9495
@Input() @InputBoolean() nzAutoFocus = false;
@@ -109,8 +110,8 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On
109110
private cdr: ChangeDetectorRef,
110111
private focusMonitor: FocusMonitor,
111112
@Optional() private directionality: Directionality,
112-
@Optional() private nzRadioService: NzRadioService,
113-
@Optional() private nzRadioButtonDirective: NzRadioButtonDirective
113+
@Optional() @Inject(NzRadioService) private nzRadioService: NzRadioService | null,
114+
@Optional() @Inject(NzRadioButtonDirective) private nzRadioButtonDirective: NzRadioButtonDirective | null
114115
) {}
115116

116117
setDisabledState(disabled: boolean): void {
@@ -143,7 +144,21 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On
143144
this.cdr.markForCheck();
144145
});
145146
this.nzRadioService.selected$.pipe(takeUntil(this.destroy$)).subscribe(value => {
147+
const isChecked = this.isChecked;
146148
this.isChecked = this.nzValue === value;
149+
// We don't have to run `onChange()` on each `nz-radio` button whenever the `selected$` emits.
150+
// If we have 8 `nz-radio` buttons within the `nz-radio-group` and they're all connected with
151+
// `ngModel` or `formControl` then `onChange()` will be called 8 times for each `nz-radio` button.
152+
// We prevent this by checking if `isChecked` has been changed or not.
153+
if (
154+
this.isNgModel &&
155+
isChecked !== this.isChecked &&
156+
// We're only intereted if `isChecked` has been changed to `false` value to emit `false` to the ascendant form,
157+
// since we already emit `true` within the `setupClickListener`.
158+
this.isChecked === false
159+
) {
160+
this.onChange(false);
161+
}
147162
this.cdr.markForCheck();
148163
});
149164
}
@@ -193,9 +208,7 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On
193208
return;
194209
}
195210
this.ngZone.run(() => {
196-
if (this.nzRadioService) {
197-
this.nzRadioService.select(this.nzValue);
198-
}
211+
this.nzRadioService?.select(this.nzValue);
199212
if (this.isNgModel) {
200213
this.isChecked = true;
201214
this.onChange(true);

components/radio/radio.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('radio', () => {
2323
NzTestRadioGroupFormComponent,
2424
NzTestRadioGroupDisabledComponent,
2525
NzTestRadioGroupDisabledFormComponent,
26+
NzTestRadioGroupLabelNgModelComponent,
2627
NzTestRadioSingleRtlComponent,
2728
NzTestRadioGroupRtlComponent,
2829
NzTestRadioButtonRtlComponent
@@ -322,6 +323,30 @@ describe('radio', () => {
322323
}).not.toThrow();
323324
}));
324325
});
326+
describe('ngModel on the `nz-radio` button', () => {
327+
it('`onChange` of each `nz-radio` should emit correct values', () => {
328+
const fixture = TestBed.createComponent(NzTestRadioGroupLabelNgModelComponent);
329+
fixture.detectChanges();
330+
331+
const radios = fixture.debugElement.queryAll(By.directive(NzRadioComponent));
332+
333+
radios[0].nativeElement.click();
334+
expect(fixture.componentInstance.items).toEqual([
335+
{ label: 'A', checked: true },
336+
{ label: 'B', checked: false },
337+
{ label: 'C', checked: false },
338+
{ label: 'D', checked: false }
339+
]);
340+
341+
radios[1].nativeElement.click();
342+
expect(fixture.componentInstance.items).toEqual([
343+
{ label: 'A', checked: false },
344+
{ label: 'B', checked: true },
345+
{ label: 'C', checked: false },
346+
{ label: 'D', checked: false }
347+
]);
348+
});
349+
});
325350
describe('RTL', () => {
326351
it('should single radio className correct', () => {
327352
const fixture = TestBed.createComponent(NzTestRadioSingleRtlComponent);
@@ -520,6 +545,37 @@ export class NzTestRadioGroupSolidComponent {
520545
singleDisabled = false;
521546
}
522547

548+
/** https://github.com/NG-ZORRO/ng-zorro-antd/issues/7254 */
549+
@Component({
550+
template: `
551+
<nz-radio-group>
552+
<label nz-radio *ngFor="let item of items" [nzValue]="item.label" [(ngModel)]="item.checked">
553+
{{ item.label }}
554+
</label>
555+
</nz-radio-group>
556+
`
557+
})
558+
export class NzTestRadioGroupLabelNgModelComponent {
559+
items = [
560+
{
561+
label: 'A',
562+
checked: false
563+
},
564+
{
565+
label: 'B',
566+
checked: false
567+
},
568+
{
569+
label: 'C',
570+
checked: false
571+
},
572+
{
573+
label: 'D',
574+
checked: false
575+
}
576+
];
577+
}
578+
523579
@Component({
524580
template: `
525581
<div [dir]="direction">

0 commit comments

Comments
 (0)