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.

2 Answers 2

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.

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

6 Comments

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.
Update the note.ValidTo variable in angular?
I dont have no idea about that. where is this variable available? I have tried to google it but no luck!
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.
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.
|
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)
    });
  }
}

4 Comments

I think it's a simple answer. Can you suggest me any other ways to do same thing in angular2 manner.
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.
Also you can simplify context binding with arrow functions: onSelect: (date: string) => this.onSelectDate.emit(date)
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)

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.