Search code examples
angularangular-ngmodelangular2-ngmodel

Angular 2 - Binding Component to multiple values


I'm trying to implement a date range selector in Angular 2.

I already have a working widget, which I have to link to the @angular/forms subsystem.

What I would like is to be able to bind the two output values (let's say rangeStart and rangeEnd) to two distinct properties in the containing form's state.

Is there a way I can configure the NgModel settings to accomplish this?

An alternative could be to bind a single property of type DateRange:

type DateRange = {
    from: Date,
    to: Date
};

buyt I don't know if this is even possible.

Any suggestion on how to accomplish this?

Edit:

What I have is a jQuery-derived, JS component which exposes an onChange, like so:

component.on('change', (eventData) => {
  // here I have eventData.from and eventData.to as Date values
});

I want to integrate this kind of handler in a Angular friendly component. But, I can't simply do this:

<my-date-range-picker name"xyz" [(NgModel)]="aDateRangeValue"></my-date-range-picker>

Because, AFAICT, change detection is not going to work with composite values. What should I expose in my component? Two EventEmitters? Can I leverage NgModel in some way?


Solution

  • Well, as it turns out, you can have models of any kind.

    So, I used the base classes from this article, here is the most relevant:

    export class ValueAccessorBase<T> implements ControlValueAccessor {  
      private innerValue: T;
    
    
      private changed = new Array<(value: T) => void>();
      private touched = new Array<() => void>();
    
    
      get value(): T {
        return this.innerValue;
      }
    
    
      set value(value: T) {
        if (this.innerValue !== value) {
          this.innerValue = value;
          this.changed.forEach(f => f(value));
        }
      }
    
    
      touch() {
        this.touched.forEach(f => f());
      }
    
    
      writeValue(value: T) {
        this.innerValue = value;
      }
    
    
      registerOnChange(fn: (value: T) => void) {
        this.changed.push(fn);
      }
    
    
      registerOnTouched(fn: () => void) {
        this.touched.push(fn);
      }
    }
    

    This happens to work even when T is a class, with from and to properties, in my case:

    @Component(
      ...
    )
    class DateRangeComponent extends ValueAccessorBase<DateRange> {
      ... implementation
    
      // somewhere after the view init:
      jqueryComponent.on('change', (eventData) => {
       // here I have eventData.from and eventData.to as Date values
       this.value = {
         from: eventData.from,
         to: eventData.to
       };
      });
    }
    

    So, if everyone else stumbles upon this question, the answer is: go ahead and write your own component.

    As a side note, this works best when using forms only to prepare Json objects to be sent in Ajax calls. An old fashioned form-encoded submit would be less linear.