Alberto Chiesa
Alberto Chiesa

Reputation: 7360

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?

Upvotes: 3

Views: 4888

Answers (3)

Alberto Chiesa
Alberto Chiesa

Reputation: 7360

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.

Upvotes: 1

alcfeoh
alcfeoh

Reputation: 2267

If you want to create you own ngModel like two-way data-binding, that's what you should do:

@Component()
export class MyComponent {

      myValue = 0;

      @Output()
      myValueChange = new EventEmitter();

      @Input()
      get myValue() {
             return this.myValue;
      }

      set myValue(val) {
           this.myValue= val;
           this.myValueChange.emit(this.myValue);
      }
}

Now you can use it as follows and have two-way data binding in effect:

<my-component [(myValue)]="someExpression"></my-component>

Adding a link to a simple tutorial on this as well: http://www.angulartraining.com/blog/tutorial-create-your-own-two-way-data-binding-in-angular/

Upvotes: 2

anteAdamovic
anteAdamovic

Reputation: 1468

You can use two Output directives.

<my-date-range-picker name"xyz" [dateTo]="dateTo" [dateFrom]="dateFrom"></my-date-range-picker>

While in your component you would have

import { Output } from '@angular/core';
.
.
@Output() dateTo: any; // EventEmitter, Subject, Number, String, doesn't matter ...
@Output() dateFrom: any;

Here's a reference to component interaction using Input and Output

Component Interaction

Upvotes: 1

Related Questions