Angular 使用 SVG 时,ControlValueAccessor 复选框始终为真
Angular ControlValueAccessor checkbox always true when using SVG
我有一个自定义复选框组件,目前使用一些 Material Icons 图标来显示。我在几个地方使用了该组件,但我在一个特定实例上遇到了问题。
我有一个网格,左侧有多个复选框,可以一次 select 多行。当 <i>
用于显示字体中的图标时,这些复选框可以正常工作。当我改为使用 SVG 时,这些复选框会中断。我一次只能 select 一个,我不能删除select select 一个。如果我选中一个框,它会像您期望的那样进行检查。如果我再次单击它,什么也不会发生——该框保持选中状态。如果我选中不同的框,第一个未选中。
这是原来的复选框组件模板:
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
[(ngModel)]="value"
(blur)="onBlur()"
[disabled]="disabled"
>
<i class="material-icons md-18 indeterminate norowclick">indeterminate_check_box</i>
<i class="material-icons md-18 checked norowclick">check_box</i>
<i class="material-icons md-18 unchecked norowclick">check_box_outline_blank</i>
</label>
这是新模板,使用 angular-svg-icon
。呈现时,有一个 <svg-icon>
元素,其中 <svg>
元素是其唯一的子元素。 这是对任何代码所做的唯一更改,这就是导致代码中断的原因。
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
[(ngModel)]="value"
(blur)="onBlur()"
[disabled]="disabled"
>
<svg-icon name="indeterminate_check_box" class="icon-18 indeterminate norowclick"></svg-icon>
<svg-icon name="check_box" class="icon-18 checked norowclick"></svg-icon>
<svg-icon name="check_box_outline_blank" class="icon-18 unchecked norowclick"></svg-icon>
</label>
这是复选框组件代码:
@Component({
selector: 'my-checkbox',
templateUrl: './my-checkbox.component.html',
styleUrls: ['./my-checkbox.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCheckboxComponent),
multi: true
}
]
})
export class MyCheckboxComponent implements OnInit, ControlValueAccessor {
@Input() formControlName: string;
@Input() checked: boolean = false;
@Input() indeterminate: boolean = false;
@Input() disabled: boolean = false;
public control: AbstractControl;
private innerValue: any = '';
private controlContainer: ControlContainer;
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
constructor(
@Optional()
@Host()
@SkipSelf()
private _controlContainer: ControlContainer
) {
this.controlContainer = _controlContainer;
}
ngOnInit() {
this._getFormControl();
}
private _getFormControl() {
if (this.controlContainer) {
if (this.formControlName) {
this.control = this.controlContainer.control.get(this.formControlName);
}
}
}
get value(): any {
return this.innerValue;
}
set value(v: any) {
if (v !== this.innerValue) {
this.indeterminate = false;
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
网格列呈现为这样的模板。当传递给 checkSecret()
函数时,$event.target.checked
在新版本中始终为真。
<ng-template #checkBoxTemplate let-value="value" let-item="item">
<div class="rowCheck">
<span *ngIf="!item['isFolder']" [class.rowHoverInline]="!hasSelections()">
<my-checkbox class="norowclick" [checked]="item['isSelected']" (change)="checkSecret(item, $event)"></my-checkbox>
</span>
</div>
</ng-template>
我不确定,但我怀疑输入状态和事件管理有些奇怪。这是 quick-fix 的尝试,我绝对没有测试过。
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
(change)="onInputChange($event)" <-- CHANGE HERE ->
(blur)="onBlur()"
[disabled]="disabled"
>
<i class="material-icons md-18 indeterminate norowclick">indeterminate_check_box</i>
<i class="material-icons md-18 checked norowclick">check_box</i>
<i class="material-icons md-18 unchecked norowclick">check_box_outline_blank</i>
</label>
@Component({
selector: 'my-checkbox',
templateUrl: './my-checkbox.component.html',
styleUrls: ['./my-checkbox.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCheckboxComponent),
multi: true
}
]
})
export class MyCheckboxComponent implements OnInit, ControlValueAccessor {
@Input() formControlName: string;
@Input() checked: boolean = false;
@Input() indeterminate: boolean = false;
@Input() disabled: boolean = false;
public control: AbstractControl;
private innerValue: any = '';
private controlContainer: ControlContainer;
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
constructor(
@Optional()
@Host()
@SkipSelf()
private _controlContainer: ControlContainer
) {
this.controlContainer = _controlContainer;
}
ngOnInit() {
this._getFormControl();
}
private _getFormControl() {
if (this.controlContainer) {
if (this.formControlName) {
this.control = this.controlContainer.control.get(this.formControlName);
}
}
}
// CHANGE HERE
onInputChange(event) {
const newValue: boolean = event.target.checked;
if (newValue !== this.innerValue) {
this.indeterminate = false;
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
除此之外,我想告诉你,(根据我的理解)你应该在调用自定义控件值访问器时使用 [(ngModel)]。喜欢 :
<my-checkbox class="norowclick" [(ngModel)]="item['isSelected']"></my-checkbox>
或者,如果你真的想抓住 ngModel 的变化来做一些除了更新值之外的事情:
<my-checkbox class="norowclick" [ngModel]="item['isSelected']" (ngModelChange)="checkSecret(item, $event)"></my-checkbox>
然后覆盖的 writeValue 可用于更新您的内部模型 (innerValue) 以捕获来自父组件的更改事件。
要将模型更改从您的 ControlValueAccessor 发送回您的父组件,您可以使用在 registerOnChange 中注册的回调,我认为您做对了。
另外我觉得变量 checked
和 innerValue
可能有点多余。
我有一个自定义复选框组件,目前使用一些 Material Icons 图标来显示。我在几个地方使用了该组件,但我在一个特定实例上遇到了问题。
我有一个网格,左侧有多个复选框,可以一次 select 多行。当 <i>
用于显示字体中的图标时,这些复选框可以正常工作。当我改为使用 SVG 时,这些复选框会中断。我一次只能 select 一个,我不能删除select select 一个。如果我选中一个框,它会像您期望的那样进行检查。如果我再次单击它,什么也不会发生——该框保持选中状态。如果我选中不同的框,第一个未选中。
这是原来的复选框组件模板:
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
[(ngModel)]="value"
(blur)="onBlur()"
[disabled]="disabled"
>
<i class="material-icons md-18 indeterminate norowclick">indeterminate_check_box</i>
<i class="material-icons md-18 checked norowclick">check_box</i>
<i class="material-icons md-18 unchecked norowclick">check_box_outline_blank</i>
</label>
这是新模板,使用 angular-svg-icon
。呈现时,有一个 <svg-icon>
元素,其中 <svg>
元素是其唯一的子元素。 这是对任何代码所做的唯一更改,这就是导致代码中断的原因。
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
[(ngModel)]="value"
(blur)="onBlur()"
[disabled]="disabled"
>
<svg-icon name="indeterminate_check_box" class="icon-18 indeterminate norowclick"></svg-icon>
<svg-icon name="check_box" class="icon-18 checked norowclick"></svg-icon>
<svg-icon name="check_box_outline_blank" class="icon-18 unchecked norowclick"></svg-icon>
</label>
这是复选框组件代码:
@Component({
selector: 'my-checkbox',
templateUrl: './my-checkbox.component.html',
styleUrls: ['./my-checkbox.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCheckboxComponent),
multi: true
}
]
})
export class MyCheckboxComponent implements OnInit, ControlValueAccessor {
@Input() formControlName: string;
@Input() checked: boolean = false;
@Input() indeterminate: boolean = false;
@Input() disabled: boolean = false;
public control: AbstractControl;
private innerValue: any = '';
private controlContainer: ControlContainer;
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
constructor(
@Optional()
@Host()
@SkipSelf()
private _controlContainer: ControlContainer
) {
this.controlContainer = _controlContainer;
}
ngOnInit() {
this._getFormControl();
}
private _getFormControl() {
if (this.controlContainer) {
if (this.formControlName) {
this.control = this.controlContainer.control.get(this.formControlName);
}
}
}
get value(): any {
return this.innerValue;
}
set value(v: any) {
if (v !== this.innerValue) {
this.indeterminate = false;
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
网格列呈现为这样的模板。当传递给 checkSecret()
函数时,$event.target.checked
在新版本中始终为真。
<ng-template #checkBoxTemplate let-value="value" let-item="item">
<div class="rowCheck">
<span *ngIf="!item['isFolder']" [class.rowHoverInline]="!hasSelections()">
<my-checkbox class="norowclick" [checked]="item['isSelected']" (change)="checkSecret(item, $event)"></my-checkbox>
</span>
</div>
</ng-template>
我不确定,但我怀疑输入状态和事件管理有些奇怪。这是 quick-fix 的尝试,我绝对没有测试过。
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
(change)="onInputChange($event)" <-- CHANGE HERE ->
(blur)="onBlur()"
[disabled]="disabled"
>
<i class="material-icons md-18 indeterminate norowclick">indeterminate_check_box</i>
<i class="material-icons md-18 checked norowclick">check_box</i>
<i class="material-icons md-18 unchecked norowclick">check_box_outline_blank</i>
</label>
@Component({
selector: 'my-checkbox',
templateUrl: './my-checkbox.component.html',
styleUrls: ['./my-checkbox.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCheckboxComponent),
multi: true
}
]
})
export class MyCheckboxComponent implements OnInit, ControlValueAccessor {
@Input() formControlName: string;
@Input() checked: boolean = false;
@Input() indeterminate: boolean = false;
@Input() disabled: boolean = false;
public control: AbstractControl;
private innerValue: any = '';
private controlContainer: ControlContainer;
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
constructor(
@Optional()
@Host()
@SkipSelf()
private _controlContainer: ControlContainer
) {
this.controlContainer = _controlContainer;
}
ngOnInit() {
this._getFormControl();
}
private _getFormControl() {
if (this.controlContainer) {
if (this.formControlName) {
this.control = this.controlContainer.control.get(this.formControlName);
}
}
}
// CHANGE HERE
onInputChange(event) {
const newValue: boolean = event.target.checked;
if (newValue !== this.innerValue) {
this.indeterminate = false;
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
除此之外,我想告诉你,(根据我的理解)你应该在调用自定义控件值访问器时使用 [(ngModel)]。喜欢 :
<my-checkbox class="norowclick" [(ngModel)]="item['isSelected']"></my-checkbox>
或者,如果你真的想抓住 ngModel 的变化来做一些除了更新值之外的事情:
<my-checkbox class="norowclick" [ngModel]="item['isSelected']" (ngModelChange)="checkSecret(item, $event)"></my-checkbox>
然后覆盖的 writeValue 可用于更新您的内部模型 (innerValue) 以捕获来自父组件的更改事件。
要将模型更改从您的 ControlValueAccessor 发送回您的父组件,您可以使用在 registerOnChange 中注册的回调,我认为您做对了。
另外我觉得变量 checked
和 innerValue
可能有点多余。