Angular ControlValueAccessor使用SVG时复选框始终为true

9
我有一个自定义复选框组件,目前使用了一些 Material Icons 图标进行显示。我在几个地方使用该组件,但是在某个特定实例中遇到了问题。
我有一个带有多个复选框的网格,位于左侧以一次选择多行。当使用<i>来显示字体中的图标时,这些复选框可以正常工作。当我改用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"
  >
  <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始终为true。
<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>

有可能让已选中的SVG覆盖在输入框上,以防止用户触发点击事件吗? - Julien
@Julien 点击事件已经被触发,但它每次返回的结果都是复选框被选中了,而不是应该切换的样子。 - vaindil
1个回答

6

我不确定,但我怀疑输入状态和事件管理存在一些奇怪的问题。这里是一个快速修复尝试,但我绝对没有测试。

<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中注册的回调函数,我认为您做得很对。

此外,我感觉变量checkedinnerValue可能有点多余。


1
问题已经解决,谢谢!(唯一的更改是我使用了问题中的<svg-icon>元素而不是<i>。)我原本没有编写这个组件,只是无法弄清如何更改它以便与新的图标元素配合使用。我还必须使用分离的[ngModel]语法,当选中/取消选中一个框时,网格必须进行一些计算。再次感谢! - vaindil
v 必须在您的版本中为 event - Frank N
onInputChange不在你的控制器中吗? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接