Angular组件库ng-zorro-antd实现radio单选框选择

antd组件库升级之后代码不生效

项目业务之前的代码设计了类似radio单选框取消选择的相关逻辑,用的是下面类似的代码实现的。但近期对ng-zorro-antd组件库升级之后,下面的代码不生效了。

import { Component } from '@angular/core';
@Component({
 selector: 'nz-demo-radio-radiogroup',
 template: `
 <nz-radio-group [(ngModel)]="radioValue">
 <label nz-radio (click)="click('A')" nzValue="A">A</label>
 <label nz-radio nzValue="B">B</label>
 <label nz-radio nzValue="C">C</label>
 <label nz-radio nzValue="D">D</label>
 </nz-radio-group>
 `
})
export class NzDemoRadioRadiogroupComponent {
 radioValue = 'A';
 click(value: any) {
 if (this.radioValue === value) {
 this.radioValue = ''
 }
 }
}

于是我到组件库源码里去找原因,并写demo打断点调试

  • 将nz-radio-group绑定的radioValue值清空会首先走group组件下面的逻辑
writeValue(value: NzSafeAny): void {
 this.value = value;
 this.nzRadioService.select(value);
 this.cdr.markForCheck();
 }
  • nz-radio 和 nz-radio-group都是共用这个NzRadioService,且组件内init时都有对selected$这个流做监听
@Injectable()
export class NzRadioService {
 selected$ = new ReplaySubject<NzSafeAny>(1);
 touched$ = new Subject<void>();
 disabled$ = new ReplaySubject<boolean>(1);
 name$ = new ReplaySubject<string>(1);
 touch(): void {
 this.touched$.next();
 }
 select(value: NzSafeAny): void {
 this.selected$.next(value);
 }
 setDisabled(value: boolean): void {
 this.disabled$.next(value);
 }
 setName(value: string): void {
 this.name$.next(value);
 }
}
// radio.component.ts ====> ngOnInit
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();
 });
  • 当监听完值改变后,后面又执行了radio的click事件,把点击哪个radio的value值传过去了,所以之前的清空值操作就被覆盖了。
private setupClickListener(): void {
 this.ngZone.runOutsideAngular(() => {
 fromEvent<MouseEvent>(this.elementRef.nativeElement, 'click')
 .pipe(takeUntil(this.destroy$))
 .subscribe(event => {
 /** prevent label click triggered twice. **/
 event.stopPropagation();
 event.preventDefault();
 if (this.nzDisabled || this.isChecked) {
 return;
 }
 this.ngZone.run(() => {
 // !!! again 
 this.nzRadioService?.select(this.nzValue);
 if (this.isNgModel) {
 this.isChecked = true;
 this.onChange(true);
 }
 this.cdr.markForCheck();
 });
 });
 });
 }

解决方法

清空值的操作加setTimeout 使组件库内部先执行完click后续再执行。

import { Component } from '@angular/core';
@Component({
selector: 'nz-demo-radio-radiogroup',
template: `
 <nz-radio-group [(ngModel)]="radioValue">
 <label nz-radio (click)="click('A')" nzValue="A">A</label>
 <label nz-radio nzValue="B">B</label>
 <label nz-radio nzValue="C">C</label>
 <label nz-radio nzValue="D">D</label>
 </nz-radio-group>
`
})
export class NzDemoRadioRadiogroupComponent {
radioValue = 'A';
click(value: any) {
 if (this.radioValue === value) {
 setTimeout(()=>{
 this.radioValue = ''
 })
 }
}
}

总结

其实组件库单选radio本身是不支持取消选择的,正解应该是用checkbox实现相关的业务逻辑才对,但很久之前的业务逻辑涉及到很多地方的修改,此时再换checkbox并且换样式的话,改动的还是比较大的,就先简单解决这个问题。

作者:后青春期的诗

%s 个评论

要回复文章请先登录注册