diff --git a/components/radio/radio.component.ts b/components/radio/radio.component.ts index 917ef94758..e5b5fd924e 100644 --- a/components/radio/radio.component.ts +++ b/components/radio/radio.component.ts @@ -12,6 +12,7 @@ import { Component, ElementRef, forwardRef, + Inject, Input, NgZone, OnDestroy, @@ -88,7 +89,7 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On isRadioButton = !!this.nzRadioButtonDirective; onChange: OnChangeType = () => {}; onTouched: OnTouchedType = () => {}; - @ViewChild('inputElement', { static: false }) inputElement?: ElementRef; + @ViewChild('inputElement', { static: true }) inputElement!: ElementRef; @Input() nzValue: NzSafeAny | null = null; @Input() @InputBoolean() nzDisabled = false; @Input() @InputBoolean() nzAutoFocus = false; @@ -109,8 +110,8 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On private cdr: ChangeDetectorRef, private focusMonitor: FocusMonitor, @Optional() private directionality: Directionality, - @Optional() private nzRadioService: NzRadioService, - @Optional() private nzRadioButtonDirective: NzRadioButtonDirective + @Optional() @Inject(NzRadioService) private nzRadioService: NzRadioService | null, + @Optional() @Inject(NzRadioButtonDirective) private nzRadioButtonDirective: NzRadioButtonDirective | null ) {} setDisabledState(disabled: boolean): void { @@ -143,7 +144,21 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On this.cdr.markForCheck(); }); this.nzRadioService.selected$.pipe(takeUntil(this.destroy$)).subscribe(value => { + const isChecked = this.isChecked; this.isChecked = this.nzValue === value; + // We don't have to run `onChange()` on each `nz-radio` button whenever the `selected$` emits. + // If we have 8 `nz-radio` buttons within the `nz-radio-group` and they're all connected with + // `ngModel` or `formControl` then `onChange()` will be called 8 times for each `nz-radio` button. + // We prevent this by checking if `isChecked` has been changed or not. + if ( + this.isNgModel && + isChecked !== this.isChecked && + // We're only intereted if `isChecked` has been changed to `false` value to emit `false` to the ascendant form, + // since we already emit `true` within the `setupClickListener`. + this.isChecked === false + ) { + this.onChange(false); + } this.cdr.markForCheck(); }); } @@ -193,9 +208,7 @@ export class NzRadioComponent implements ControlValueAccessor, AfterViewInit, On return; } this.ngZone.run(() => { - if (this.nzRadioService) { - this.nzRadioService.select(this.nzValue); - } + this.nzRadioService?.select(this.nzValue); if (this.isNgModel) { this.isChecked = true; this.onChange(true); diff --git a/components/radio/radio.spec.ts b/components/radio/radio.spec.ts index 8a38c5473b..370917fea7 100644 --- a/components/radio/radio.spec.ts +++ b/components/radio/radio.spec.ts @@ -23,6 +23,7 @@ describe('radio', () => { NzTestRadioGroupFormComponent, NzTestRadioGroupDisabledComponent, NzTestRadioGroupDisabledFormComponent, + NzTestRadioGroupLabelNgModelComponent, NzTestRadioSingleRtlComponent, NzTestRadioGroupRtlComponent, NzTestRadioButtonRtlComponent @@ -322,6 +323,30 @@ describe('radio', () => { }).not.toThrow(); })); }); + describe('ngModel on the `nz-radio` button', () => { + it('`onChange` of each `nz-radio` should emit correct values', () => { + const fixture = TestBed.createComponent(NzTestRadioGroupLabelNgModelComponent); + fixture.detectChanges(); + + const radios = fixture.debugElement.queryAll(By.directive(NzRadioComponent)); + + radios[0].nativeElement.click(); + expect(fixture.componentInstance.items).toEqual([ + { label: 'A', checked: true }, + { label: 'B', checked: false }, + { label: 'C', checked: false }, + { label: 'D', checked: false } + ]); + + radios[1].nativeElement.click(); + expect(fixture.componentInstance.items).toEqual([ + { label: 'A', checked: false }, + { label: 'B', checked: true }, + { label: 'C', checked: false }, + { label: 'D', checked: false } + ]); + }); + }); describe('RTL', () => { it('should single radio className correct', () => { const fixture = TestBed.createComponent(NzTestRadioSingleRtlComponent); @@ -520,6 +545,37 @@ export class NzTestRadioGroupSolidComponent { singleDisabled = false; } +/** https://github.com/NG-ZORRO/ng-zorro-antd/issues/7254 */ +@Component({ + template: ` + + + + ` +}) +export class NzTestRadioGroupLabelNgModelComponent { + items = [ + { + label: 'A', + checked: false + }, + { + label: 'B', + checked: false + }, + { + label: 'C', + checked: false + }, + { + label: 'D', + checked: false + } + ]; +} + @Component({ template: `