Skip to content

allow null values ​​in a select and also allow customization of the comparison function #77

Description

@insinfo

@GZGavinZhao

Description

allow null values ​​in a select and also allow customization of the comparison function

I made a custom CustomSelectControlValueAccessor to allow this, I think this can be integrated into AngularDart just like it was integrated into Angular typscript

angular/angular#10349
https://stackoverflow.com/questions/47477167/use-comparewith-function-from-angular-material-mat-select-component-with-linked

// ignore_for_file: unnecessary_import, implementation_imports
import 'dart:html';
import 'package:ngdart/angular.dart';
import 'package:ngforms/ngforms.dart';
import 'package:ngforms/src/directives/control_value_accessor.dart'
    show ChangeHandler, ControlValueAccessor, ngValueAccessor, TouchHandler;

bool isPrimitive(val) {
  return val is num || val is bool || val == null || val is String;
}

/// function for compare
bool _equals(Object? a, Object? b) {
  return a == b;
}

String _buildValueString(String? id, Object? value) {
  if (id == null) return '$value';
  if (!isPrimitive(value)) value = 'Object';
  var s = '$id: $value';
  // TODO: Fix this magic maximum 50 characters (from TS-transpile).
  if (s.length > 50) {
    s = s.substring(0, 50);
  }
  return s;
}

String _extractId(String valueString) => valueString.split(':')[0];

const selectValueAccessorCustom = ExistingProvider.forToken(
  ngValueAccessor,
  CustomSelectControlValueAccessor,
);

/// The accessor for writing a value and listening to changes on a select
/// element.
///
/// Note: We have to listen to the 'change' event because 'input' events aren't
/// fired for selects in Firefox and IE:
/// https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
/// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045
@Directive(
  selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
  providers: [selectValueAccessorCustom],
  // SelectControlValueAccessor must be visible to NgSelectOption.
  visibility: Visibility.all,
)
class CustomSelectControlValueAccessor extends Object
    with TouchHandler, ChangeHandler<dynamic>
    implements ControlValueAccessor<Object?> {
  final SelectElement _element;
  Object? value;
  final Map<String, Object?> _optionMap = <String, Object?>{};
  num _idCounter = 0;

  CustomSelectControlValueAccessor(HtmlElement element)
      : _element = element as SelectElement;

  @HostListener('change', ['\$event.target.value'])
  void handleChange(String value) {
    onChange(_getOptionValue(value), rawValue: value);
  }

  @override
  void writeValue(Object? value) {
    this.value = value;
    var valueString = _buildValueString(_getOptionId(value), value);

    _element.value = valueString;
  }

  @override
  void onDisabledChanged(bool isDisabled) {
    _element.disabled = isDisabled;
  }

  String _registerOption() => (_idCounter++).toString();

  /// set custom function to Check whether two object references are to the same object.
  @Input()
  set compareWith(bool Function(Object? o1, Object? o2) fn) {
    _compareWith = fn;
  }

  /// use equals (a == b) function for compare
  @Input()
  set useEquals(bool val) {
    _compareWith = _equals;
  }

  /// use identical or custom function for compare
  bool Function(Object? o1, Object? o2) _compareWith = identical;

  String? _getOptionId(Object? value) {
    for (var id in _optionMap.keys) {
      //if (identical(_optionMap[id], value)) return id;
      if (_compareWith(_optionMap[id], value)) return id;
    }
    return null;
  }

  /// allow null values ​​in a select option
  ///  <option value="null" selected>Selecione</option>
  ///  <option ngValue="null" selected>Selecione</option>
  @Input()
  bool enableNullValue = false;

  dynamic _getOptionValue(String valueString) {
    final ngVal = _optionMap[_extractId(valueString)];

    if (enableNullValue) {
      return ngVal;
    }
    return ngVal ?? valueString;
  }
}

/// Marks <option> as dynamic, so Angular can be notified when options change.
///
/// ### Example
///
///     <select ngControl="city">
///       <option *ngFor="let c of cities" [value]="c"></option>
///     </select>
@Directive(
  selector: 'option',
)
class CustomNgSelectOption implements OnDestroy {
  final OptionElement _element;
  final CustomSelectControlValueAccessor? _select;
  late final String id;

  CustomNgSelectOption(HtmlElement element, @Optional() @Host() this._select)
      : _element = element as OptionElement {
    if (_select != null) id = _select!._registerOption();
  }

  @Input('ngValue')
  set ngValue(Object? value) {
    var select = _select;
    if (select == null) return;

    if (select.enableNullValue) {
      select._optionMap[id] = value == 'null' ? null : value;
    } else {
      select._optionMap[id] = value;
    }

    _setElementValue(_buildValueString(id, value));
    select.writeValue(select.value);
  }

  @Input('value')
  set value(Object? value) {
    var select = _select;
    _setElementValue(value as String);
    if (select != null) select.writeValue(select.value);
  }

  void _setElementValue(String value) {
    _element.value = value;
  }

  @override
  void ngOnDestroy() {
    var select = _select;
    if (select != null) {
      select._optionMap.remove(id);
      select.writeValue(select.value);
    }
  }
}

Please provide the steps to reproduce the bug

<div class="form-group col-md-2 mb-3">
            <label class="form-label">Estado Civil:</label>

            <select
              class="form-select"
              [(ngModel)]="matricula.candidato.estadoCivil"
              ngControl="estadoCivil"
              [enableNullValue]="true"
            >
              <option value="null" selected>Selecione</option>
              <option
                *ngFor="let estadoCivil of estadoCivilList "
                [ngValue]="estadoCivil"
              >
                {{estadoCivil}}
              </option>
            </select>
          </div>

Please provide the exception or error you saw

No response

Please provide the dependency environment you discovered this bug in (run dart pub deps -s compact)

Dart SDK version: 3.2.1 (stable) (Wed Nov 22 08:59:13 2023 +0000) on "windows_x64"

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions