1


I am new to Angular2.
I am trying to implement JQuery ui datepicker in angular2.

I have written a datePicker directive, there i enabled datepicker. Here i am able to select date but finding difficulty to emit selected date to the parent component.

to overcome this i created an object on window object and passing component reference to that object. from there i am calling component function.

I feel this is not a best practice to do.

Can someone help me to do in right way.

import { Directive, ElementRef, Input, NgZone,HostListener,Output,EventEmitter } from '@angular/core';
    declare  var $:any;
    @Directive({
      selector: '[uiDatePicker]',
     })
    export class UiDatePickerDirective {
      @Input('uiDatePicker') setDate: string;
      @Output() onSelectDate = new EventEmitter();  
      private el: HTMLElement;
      constructor(el: ElementRef,public zone:NgZone) {
        this.el = el.nativeElement;

        window.angularComponentRef = {
          zone: this.zone, 
          component: this
        };
      }

      doEmitDate(dateText:string){
          this.onSelectDate.emit(dateText);
      }
      ngOnInit() {
        $(this.el).datepicker({
         onSelect: function(dateText:string) {
            window.angularComponentRef.component.doEmitDate(dateText);
         }
        });
       }
}

Here i dont like to use window.angularComponentRef.component object. As it is just storing the reference in global object. this is not good for an application.

ayyappa maddi
  • 854
  • 3
  • 10
  • 18

2 Answers2

2

import { Directive, ElementRef, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";

declare var $: any;

export const CUSTOM_INPUT_DATE_PICKER_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => UiDatePickerDirective),
    multi: true
};

@Directive({
    selector: '[uiDatePicker]',
    host: { '(blur)': 'onTouched($event)' },
    providers: [CUSTOM_INPUT_DATE_PICKER_CONTROL_VALUE_ACCESSOR]
})
export class UiDatePickerDirective implements ControlValueAccessor {
    private innerValue: string;

    @Input('changeMonth') changeMonth: boolean = true;
    @Input('changeYear') changeYear: boolean = true;

    constructor(private el: ElementRef) {
    }

    ngOnInit() {
        $(this.el.nativeElement).datepicker({
            changeMonth: true,
            changeYear: true,
            dateFormat: 'dd/mm/yy'
        }).on('change', (e: any) => {
            this.onChange(e.target.value);
        });
    }


    public onChange: any = (_:any) => { /*Empty*/ }
    public onTouched: any = () => { /*Empty*/ }

    get value(): any {
        return this.innerValue;
    };

    //set accessor including call the onchange callback
    set value(v: any) {
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChange(v);
        }
    }


    writeValue(val: string): void {
        this.innerValue = val;
        $(this.el.nativeElement).datepicker("setDate", this.innerValue);
    }

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

    registerOnTouched(fn: any): void {
        this.onTouched = fn;
    }
}
<input type="text" uiDatePicker [(ngModel)]="note.ValidTo"/>

This is a modified version from here.

Community
  • 1
  • 1
kravits88
  • 12,431
  • 1
  • 51
  • 53
  • i have implemented this solution in my application. One problem i am facing is how to set a date pragmatically in textbox which has this directive. I am using Form Builder to set data. – Praveen Rawat Sep 16 '17 at 08:45
  • Update the note.ValidTo variable in angular? – kravits88 Sep 16 '17 at 08:55
  • I dont have no idea about that. where is this variable available? I have tried to google it but no luck! – Praveen Rawat Sep 16 '17 at 09:05
  • I can pass input field with date and set it in `OnInIt()` method like this `$(this.el.nativeElement).val(value);`. But its a work around. – Praveen Rawat Sep 16 '17 at 09:17
  • Ngmodel is the variable angular uses in the directive. You can get and set the date with this attribute. You should read up on ngmodel. – kravits88 Sep 16 '17 at 10:04
  • I am using reactive forms, problem was directive was not detecting change of value so i had to implement `OnChanges` to update my value. Thanks – Praveen Rawat Sep 16 '17 at 11:28
1

For all intents and purposes, you can use the var self = this ideology and closures to maintain a reference to the component.

If you aren't doing any pre-processing in the onSelect callback, you can further simplify things by passing a bound doEmitDate function as the onSelect handler.

import { Directive, ElementRef, Input, NgZone,HostListener,Output,EventEmitter } from '@angular/core';
declare  var $:any;

@Directive({
  selector: '[uiDatePicker]',
})
export class UiDatePickerDirective {
  @Input('uiDatePicker') setDate: string;
  @Output() onSelectDate = new EventEmitter();
  private el: HTMLElement;

  constructor(el: ElementRef, public zone: NgZone) {
    this.el = el.nativeElement;
  }

  doEmitDate(dateText:string){
    this.onSelectDate.emit(dateText);
  }

  ngOnInit() {
    const component = this;
    $(this.el).datepicker({
      onSelect: function(dateText: string) {
        component.doEmitDate(dateText);
      }
    });

    // Or if you're not doing anything else in the onSelect callback
    $(this.el).datepicker({
      onSelect: this.doEmitDate.bind(this)
    });
  }
}
hotforfeature
  • 2,558
  • 1
  • 16
  • 24
  • I think it's a simple answer. Can you suggest me any other ways to do same thing in angular2 manner. – ayyappa maddi Jun 08 '16 at 04:00
  • There isn't an Angular-specific way to do something like this. Grabbing the `nativeElement` using `ElementRef` is the only thing to keep in mind when passing elements to external libraries in these situations, and you're doing just that. Sometimes the simple answer is the correct answer, no need to complicate. – hotforfeature Jun 08 '16 at 04:16
  • Also you can simplify context binding with arrow functions: `onSelect: (date: string) => this.onSelectDate.emit(date)` – Yury Tolochko Jan 21 '17 at 06:35
  • One more note: jQuery components often trigger a lot of angular2 change detection circles. To prevent it you should run jQuery bindings outside angular zone (ngZone.runOutside) – Yury Tolochko Jan 21 '17 at 06:37