3

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?

8
  • Can you provide us with some code or better explanation because I'm not sure what you want to accomplish here. Commented Aug 3, 2017 at 14:28
  • I tried to better describe my scenario. Basically I have two values, and I want to expose both as output via NgModel, or something alternative Commented Aug 3, 2017 at 14:37
  • Does my answer help with your issue ? You can make them both EventEmitters yes or even Subjects (from rxjs) depending on your needs. Commented Aug 3, 2017 at 14:45
  • Post a plunker of what you've got so far. I'll be easier to work on then... Commented Aug 3, 2017 at 14:47
  • @JGFMK I have other 5 controls binding to simple values, exposing values to the forms module, through ControlValueAccessor interface. ControlValueAccessor gives a lot of benefits, but seems limited to exposing single value. Now I'm wondering how could I do the same (implementing a ControlValueAccessor) with 2 values (or, in other words, a single value defined by two dates). Commented Aug 3, 2017 at 14:50

3 Answers 3

2

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/

Sign up to request clarification or add additional context in comments.

Comments

1

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

2 Comments

Yes, but could I attach validation and form binding to simple Output directives? I'm trying to understand what limits are there in the possible approaches.
If you use them as EventEmitters or Subjects you can subscribe to them and use validation on those events
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.