Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(rating): rating does not support model-driven form #298

Closed
hongbo-miao opened this issue Mar 19, 2016 · 6 comments
Closed

chore(rating): rating does not support model-driven form #298

hongbo-miao opened this issue Mar 19, 2016 · 6 comments

Comments

@hongbo-miao
Copy link
Contributor

The way in sample works well:
<rating [(ngModel)]="rate" max="5"></rating>

But if change to model-driven form, it will show the error:

EXCEPTION: No value accessor for '' in [rate in TestComponent]

import { Component } from 'angular2/core';
import { FormBuilder, ControlGroup, AbstractControl } from 'angular2/common';

@Component({
  selector: 'test-component',
  viewProviders: [FormBuilder],
  template: `    
    <form (ngSubmit)="submit()" [ngFormModel]="form">
      <rating [ngFormControl]="rate" max="5"></rating>
      <button type="submit">Submit</button>
    </form>
  `
})
export class TestComponent {
  form: ControlGroup;
  rate: AbstractControl;

  constructor(private _formBuilder: FormBuilder) {}

  ngOnInit() {
    this.form = this._formBuilder.group({
      'rate': [3]   // or ['3'], neither works
    });

    this.rate = this.form.controls['rate'];
  }

  submit() {}
}
@valorkin
Copy link
Member

Yes, it doesn't by design
What is pros of using form control over ngmodel?

@mkjeff
Copy link
Contributor

mkjeff commented Mar 21, 2016

I think the main issue was why the component need explict inject NgModel?

class MyComp implements ControlValueAccessor
  constructor(@Self() public cd:NgModel) { // here
    cd.valueAccessor = this;
  }
}

It is really unnecessary and worst case will become cyclic dependency. Best way is lets framework to take over.

You can check out the usage of selectValueAccessor/setUpControl in angular.

Here is a custom select comonent base on dropdown directive, it work well with ngModel/ngControl/ngFormControl.

import {Component, Directive, Renderer, ElementRef, Host, Self, Optional, Input, OnInit, Provider,
  provide, forwardRef, HostListener, Inject} from 'angular2/core';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, NgModel } from 'angular2/common';
import {CONST_EXPR} from 'angular2/src/facade/lang';

import {DROPDOWN_DIRECTIVES} from '../../ng2-bootstrap/ng2-bootstrap';


@Directive({ selector: '[select-item]' })
export class SelectItem implements OnInit {
  /* tslint:disable:input-property-directive */
  @Input('select-item') private value: any;

  constructor(
    @Host()
    @Inject(forwardRef(() => SelectComponent))
    private select: SelectComponent) {
  }

  ngOnInit() {
    const getter = this.select.valueGetter;
    if (getter && this.value) {
      const controlValue = typeof getter === 'string' ? this.value[getter] : getter(this.value);
      if (controlValue === this.select.value)
        setTimeout(() => this.select.value = this.value);
    }
  }

  @HostListener('click')
  private onClick() {
    return this.select.updateFromView(this.value);
  }
}

const CUSTOM_VALUE_ACCESSOR = CONST_EXPR(new Provider(NG_VALUE_ACCESSOR,
  { useExisting: forwardRef(() => SelectComponent), multi: true }));

@Component({
  selector: 'my-select',
  template:
  `<div class="btn-group" dropdown keyboardNav="true">
          <button class="btn" [ngClass]="invalidNgClass" type="button" dropdownToggle>
            {{text}}
            <span class="caret"></span>
          </button>
          <ul class="dropdown-menu" role="menu" dropdownMenu>
            <li role="menuitem" *ngFor="#item of options">
              <a class="dropdown-item" href="javascript:void(0)" [select-item]="item">
                {{getItemText(item)}}
              </a>
            </li>
          </ul>
        </div>`,
  providers: [CUSTOM_VALUE_ACCESSOR],
  directives: [...DROPDOWN_DIRECTIVES, SelectItem]
})
export class SelectComponent implements ControlValueAccessor, OnInit {
  public value: any;
  @Input() public caption: string = 'Select ...';
  @Input() public valueGetter: string | ((any) => any);
  @Input() public textGetter: string | ((any) => string);
  @Input() public invalidClass: string = '';
  @Input() public set invalid(value: boolean) {
    if (value) {
      this.invalidNgClass.push(this.invalidClass);
    } else {
      this.invalidNgClass.splice(this.invalidNgClass.indexOf(this.invalidClass), 1);
    }
  }
  @Input() public options: Array<any> = [];

  private invalidNgClass: Array<string> = [];

  constructor() {
  }

  ngOnInit() { }

  public get text() {
    const text = this.getItemText(this.value);
    if (text !== undefined)
      return text;
    return this.caption;
  }

  /**
   * (ControlValueAccessor.writeValue implement)
   * model -> view
   * @param {*} value (value to write)
   */
  public writeValue(value: any) {
    this.value = value;
  }

  /**
   * (From view(UI event) writes back to model)
   * view -> model
   * @param value (value to write)
   */
  public updateFromView(value) {
    this.value = value;
    this.onChange(this.convertToControlValue(value));
  }

  private onChange = (_: any) => { };
  private onTouched = () => { };

  public registerOnChange(fn: (_: any) => {}): void {
    this.onChange = fn;
  }

  public registerOnTouched(fn: () => {}): void {
    this.onTouched = fn;
  }

  private getItemText(value) {
    const getter = this.textGetter;
    if (getter && value) {
      if (typeof getter === 'string') {
        return value[getter];
      } else {
        return getter(value);
      }
    }
    return undefined;
  }

  private convertToControlValue(value) {
    const getter = this.valueGetter;
    if (getter && this.value) {
      if (typeof getter === 'string') {
        return value[getter];
      } else {
        return getter(value);
      }
    }
    return value;
  }
}

@valorkin
Copy link
Member

Yes, some components are implemented like this, good point for PR :)

@valorkin
Copy link
Member

BTW better to extend default value accessor, as far as I remember

@mkjeff
Copy link
Contributor

mkjeff commented Mar 21, 2016

created a PR

but typeahead seems too complex need more work on refactor

@valorkin
Copy link
Member

check typeahead related branch first, guys were trying to refactor it before already
@mkjeff I have seen your PR, it looks good
but what I am afraid of is creating value accessors and providers
I want to give a try to default value accessors or extending ngcontrol
but most probably ngModel and custom VA is the only working options

@valorkin valorkin changed the title Rating does not support model-driven form chore(rating): rating does not support model-driven form Mar 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants