【问题标题】:Angular 5, Angular Material Checkbox with 3 states (checked, unchecked, indeterminate)Angular 5,具有 3 种状态的 Angular 材质复选框(选中、未选中、不确定)
【发布时间】:2018-08-24 01:31:08
【问题描述】:

我是 Angular 和 Angular Material 的新手,现在我在某个项目中担任支持人员。有一个带有过滤器的网格和一个复选框,用于检查网格中的用户是否处于活动状态、非活动状态或未被选中。只有两个选项(活动、非活动)会更简单,但是我必须为其设置 3 个状态:

  1. 第一次点击 - 检查是否有效
  2. 第二次点击 - 未选中为非活动状态
  3. 第三次点击 - 未选择的不确定

这是来自官方 Angular Material 文档的复选框示例: https://stackblitz.com/angular/rxdmnbxmkgk?file=app%2Fcheckbox-configurable-example.html

如何用最简单的方式制作?

【问题讨论】:

  • 在最坏的情况下,双复选框,一个用于选择/未选择,一个用于活动/非活动。三状态复选框可能是可行的,但会违反用户对经典复选框的期望,因此可能会影响用户体验。

标签: angular typescript angular-material


【解决方案1】:

@angular/material >= 9

这是一个现成的组件:

import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatCheckboxDefaultOptions, MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';

@Component({
  selector: 'app-tri-state-checkbox',
  templateUrl: './tri-state-checkbox.component.html',
  styleUrls: ['./tri-state-checkbox.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TriStateCheckboxComponent),
      multi: true,
    },
    { provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: { clickAction: 'noop' } as MatCheckboxDefaultOptions },
  ],
})
export class TriStateCheckboxComponent implements ControlValueAccessor {

  @Input() tape = [null, true, false];

  value: any;

  disabled: boolean;

  private onChange: (val: boolean) => void;
  private onTouched: () => void;

  writeValue(value: any) {
    this.value = value || this.tape[0];
  }

  setDisabledState(disabled: boolean) {
    this.disabled = disabled;
  }

  next() {
    this.onChange(this.value = this.tape[(this.tape.indexOf(this.value) + 1) % this.tape.length]);
    this.onTouched();
  }

  registerOnChange(fn: any) {
    this.onChange = fn;
  }

  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

}

和模板:

<mat-checkbox [ngModel]="value" (click)="next()" [disabled]="disabled" [indeterminate]="value === false" [color]="value === false ? 'warn' : 'accent'">
  <ng-content></ng-content>
</mat-checkbox>

用法:

<app-tri-state-checkbox [(ngModel)]="done">is done</app-tri-state-checkbox>
<app-tri-state-checkbox formControlName="done">is done</app-tri-state-checkbox>

您还可以将默认磁带覆盖为例如枚举值:

<app-tri-state-checkbox [tape]="customTape" [(ngModel)]="done">is done</app-tri-state-checkbox>

其中customTape 替换默认值[null, true, false]

customTape = [Status.open, Status.complete, Status.cancelled];

@angular/material

改变一下

{ provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: { clickAction: 'noop' } as MatCheckboxDefaultOptions },

自第 9 版以来已弃用的 MAT_CHECKBOX_CLICK_ACTION

{ provide: MAT_CHECKBOX_CLICK_ACTION, useValue: 'noop' },

【讨论】:

  • 在 Angular 材料的第 9 版中:重大更改:不推荐使用 MAT_CHECKBOX_CLICK_ACTION,请使用 MAT_CHECKBOX_DEFAULT_OPTIONS。见:github.com/angular/components/releases/tag/9.0.0-next.2
  • 对于以前使用材料 8 的“noop”并以此为例移动到材料 9 的任何人,两者之间的一个重要区别是默认选项将颜色设置为“强调”。如果您现在指定自己的默认选项而不是仅单击操作,它将消除强调色。我花了几个小时试图弄清楚为什么复选框不起作用。事实证明,它只是没有显示强调色。如果要保留原始行为,请使用 { provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: { clickAction: 'noop', color: 'accent' } }
  • 我注意到在从不确定转换时动画中出现了一些闪烁 -> 检查。将值分配给 mat-checkbox 的 checked 属性而不是 ngModel 绑定似乎可以解决问题。模板:
  • 感谢 @RyanO 为我节省了时间
  • @Random 这是 ControlValueAccessor 的正确实现。 [disabled] 作为输入也可能适用于模板驱动的表单,但不适用于反应式表单
【解决方案2】:

我试图回复这个Answer,但我没有足够的声誉。首先感谢您的回答和深入的解释。我在复选框的版本上遇到了问题,我发现如果此行的值为 false

this.value = value || this.tape[0];

不起作用,颜色也没有更新。我为这个改了

this.value = value !== null ? value : this.tape[0];

我希望此评论对其他人有所帮助。

【讨论】:

  • 谢谢 - 这解决了我的问题,赞成给你更多的代表
【解决方案3】:

我想要一个解决方案,而不需要另一个组件并且采用反应形式,这似乎确实可以解决问题,尽管在一个过渡中存在重复的动画:

component.ts

  onChangeCheckbox(checkbox: MatCheckbox): void {
    if (checkbox.indeterminate){
      checkbox.indeterminate = false;
      checkbox.checked = true;
    } 
    // by the time you click it is changed
    else if (!checkbox.indeterminate && !checkbox.checked ){
      checkbox.checked = false;
    }else if (!checkbox.indeterminate && checkbox.checked ){
      checkbox.indeterminate = true;
    }
  }

component.html

<mat-checkbox
                        #checkboxName
                        formControlName="checkboxName"
                        indeterminate="true"
                        (change)="onChangeCheckbox(checkboxName)"
                        id="edit:checkboxName"> 
                        Checkbox Name
                    </mat-checkbox>

【讨论】:

  • 将 MatCheckbox 传递给函数的想法是我所需要的。就我而言,我不复制任何东西。谢谢! :)
【解决方案4】:

执行此操作的一种方法是将 MAT_CHECKBOX_CLICK_ACTION 设置为“noop”,然后您必须使用(单击)设置选中的值。不要忘记绑定 [ngModel] 和 [indeterminate]。

providers: [
    {provide: MAT_CHECKBOX_CLICK_ACTION, useValue: 'noop'}
]

看看这个:https://github.com/angular/material2/blob/master/src/lib/checkbox/checkbox.md

【讨论】:

    【解决方案5】:

    如果您需要一个工作示例,您还可以克隆 ma​​terial2 项目here,然后:

    cd material2
    npm i
    npm run demo-app
    

    打开演示应用并导航到复选框组件。

    【讨论】:

      【解决方案6】:

      感谢您的解决方案!在这里使用 Angular 9.1.7。

      我需要一组自定义值。这不是开箱即用的。感谢这个 (post) 启发我创建 2-way binding 的解决方案

      我将部分更改如下:

      模板:

      <mat-checkbox (click)="next()"
                    [(ngModel)]="guiValue"
                    [disabled]="disabled"
                    [indeterminate]="isIndeterminate"
                    [color]="isIndeterminate ? 'warn' : 'accent'"
      ><ng-content></ng-content>
      </mat-checkbox>
      

      打字稿:

      import {Component, EventEmitter, forwardRef, Input, Output} from '@angular/core';
      import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
      import { MatCheckboxDefaultOptions, MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
      
      @Component({
        selector: 'app-tri-state-checkbox',
        templateUrl: './tri-state-checkbox.component.html',
        styleUrls: ['./tri-state-checkbox.component.css'],
        providers: [
          {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => TriStateCheckboxComponent),
            multi: true,
          },
          { provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: { clickAction: 'noop' } as MatCheckboxDefaultOptions },
        ],
      })
      export class TriStateCheckboxComponent implements ControlValueAccessor {
        internalValue: any;
        guiValue: any;
        disabled: boolean;
        isIndeterminate: boolean;
      
        @Input() states = [null, true, false];
        @Output() triStateValueChange = new EventEmitter<any>();
      
        @Input()
        get triStateValue(): any {
          return this.internalValue;
        }
      
        set triStateValue(v) {
          this.internalValue = v;
          this.writeValue();
        }
      
      
        onChange = (x: any) => {console.log(`onChange:${x}`); };
        onTouched = () => {};
      
      
        writeValue() {
          if      (this.internalValue === this.states[0]) { this.guiValue = true; }  // undetermined
          else if (this.internalValue === this.states[1]) { this.guiValue = true; }  // true
          else if (this.internalValue === this.states[2]) { this.guiValue = false; } // false
          else { console.error (`Wrong value for tri state checkbox : ${this.internalValue}`); }
      
          this.isIndeterminate = ( this.internalValue === this.states[0] );
        }
      
        setDisabledState(disabled: boolean) {
          this.disabled = disabled;
        }
      
        next() {
          this.determineNextValue();
          this.onTouched();
          this.onChange(this.guiValue);
        }
        determineNextValue(){
          if      (this.internalValue === this.states[0]) {this.internalValue = this.states[1]; }
          else if (this.internalValue === this.states[1]) {this.internalValue = this.states[2]; }
          else if (this.internalValue === this.states[2]) {this.internalValue = this.states[0]; }
      
          this.triStateValueChange.emit(this.internalValue);
        }
      
        registerOnChange(fn: any) {
          this.onChange = fn;
        }
      
        registerOnTouched(fn: any) {
          this.onTouched = fn;
        }
      
      }
      

      及用法:

      模板:

      <app-tri-state-checkbox [states]="locTriState" [(triStateValue)]="targetVariable" ">Labeltext</app-tri-state-checkbox>
      

      并使用组件设置源 (TS):(变量 targetVariable 用作使用 2 路绑定的源/目标。

      export enum TriStateValues {
        on,
        off,
        dontcare
      };
      
      let targetVariable = TriStateValues.on;
      const locTriState = [ TriStateValues.dontcare, TriStateValues.on ,  TriStateValues.off];
      

      【讨论】:

        【解决方案7】:

        @angular/材质 >= 9

        在我的场景中,它有效。必须提供color: 'primary'

         providers: [{ provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: { clickAction: 'noop', color: 'primary' } as MatCheckboxDefaultOptions }]
        

        【讨论】:

          猜你喜欢
          • 2019-06-30
          • 1970-01-01
          • 2020-11-13
          • 1970-01-01
          • 2021-10-03
          • 1970-01-01
          • 2017-01-06
          • 2018-04-19
          • 1970-01-01
          相关资源
          最近更新 更多