Search code examples
angulardartangular-materialangular-formsaccessor

Explicitly specify type for material-dropdown-select instead of dynamic


Task

In AngularDart I want to choosing multiple Foo objects via material-dropdown-select and store selected values in the reactive form.

My approach

    import 'package:angular/angular.dart';
    import 'package:angular_components/angular_components.dart';
    import 'package:angular_forms/angular_forms.dart';

    @Component(
      selector: 'my-app',
      template: '''
        <form [ngFormModel]="form">
          <material-dropdown-select 
            multi 
            ngControl="control1"
            [options]="options"
            [itemRenderer]="renderer"
          >
          </material-dropdown-select>
        </form>
      ''',
      directives: [
        formDirectives,
        MaterialDropdownSelectComponent,
        MultiDropdownSelectValueAccessor,
      ],
      providers: [
        materialProviders,
      ],
    )
    class AppComponent {
      var options = SelectionOptions.fromList([Foo(50, 'op1'), Foo(51, 'op2'), Foo(52, 'op3')]);
      var form;

      AppComponent() {
        form = ControlGroup({
          'control1': Control<List<Foo>>(<Foo>[]),
        });
      }

      String renderer(Foo entry) => '${entry.id}: ${entry.title}';
    }

    class Foo {
      int id;
      String title;

      Foo(this.id, this.title);
    }

The problem

My custom renderer can't be applied because of exception from template code:

EXCEPTION: Expected a value of type '(dynamic) => String', but got one of type '(Foo) => String'

Where from dynamic has appeared? I thought the value accessor will takes the Foo type from options! Without that renderer the component appears, but choosing any item throws next:

Error: Type 'List<dynamic>' should be 'List<Foo>' to implement expected type 'List<Foo>'.

It throws from the material_dropdown_material_accessor.dart file, MultiDropdownSelectValueAccessor class:

    @override
    void registerOnChange(callback) {
      selectionChangesSub = selectionModel.selectionChanges.listen((_) {
        callback(selectionModel.selectedValues?.toList());
      });
    }

And again — where that dynamic appears from? Is it real to using material dropdown with custom types?


Solution

  • Root cause

    The problem was in the fact that MaterialDropdownSelectComponent<T> and MultiDropdownSelectValueAccessor<T> are generic directives. T is dynamic by default.

    Solution

    There is an experimental AngularDart feature — directiveTypes. It allows to specify generic type for any directive in the component's template. In my case missing lines are the following:

        @Component(
            ...
            directiveTypes: [
                Typed<MaterialDropdownSelectComponent<Foo>>(),
                Typed<MultiDropdownSelectValueAccessor<Foo>>(),
            ],
        )
        class AppComponent {
            ...
        }
    

    I've stumbled on it at the GitHub issue. It's curious what TypeScript users do for working around that.