Radzen Professional is now 20% off until January 4th 2019! Learn more

jQuery plugins and Angular 2

Checkout Radzen Demo application and download latest Radzen!

Updated on October 26, 2017 to fix the plunkr demos and update them to Angular 4.

In this blog post I will show you how to use a jQuery plugin in an Angular 4 app. The plugin is the popular jQuery UI DatePicker.

Native date inputs are not widely available even though it is 2016. The HTML5 standard includes <input type="date"> and <input type="datetime-local"> but FireFox, desktop Safari and Internet Explorer do not support them at the time of this writing. jQuery UI DatePicker comes to the rescue!

JavaScript and CSS files

Before using a jQuery plugin we must include its dependencies in our page. We include jQuery, the JavaScript implementation and the CSS files that provide the visual theme.

<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.11.4/themes/ui-darkness/jquery-ui.css">

DatePicker component

We are ready to implement the Angular component.

import { ViewChild, ElementRef, AfterViewInit, Component } from '@angular/core';

declare var jQuery: any;

@Component({
  selector: 'my-datepicker',
  template: `<input #input type="text">`
})
export class DatePickerComponent implements AfterViewInit {
  @ViewChild('input') input: ElementRef;

  ngAfterViewInit() {
    jQuery(this.input.nativeElement).datepicker();
  }
}

You can try it live in this plunkr demo.

Let’s explain the code so far.

  • First we declare jQuery as an ambient variable because it doesn’t come from a TypeScript file.  This is a simpler way to use jQuery from TypeScript. In production apps you should prefer TypeScript definition files.
  • The template of our component contains an input element because this is what the jQuery UI DatePicker initializes from.
  • We need a reference to the DOM node which represents that input. The ViewChild attribute maps an element from the template to a property of the component.
  • The ElementRef type is a DOM node wrapper. Angular wraps DOM nodes in order to support server-side rendering or non-browser platforms such as NativeScript. On those platforms the DOM is not available.
  • Finally we implement the ngAfterViewInit lifecycle method because at this point all ViewChild properties are ready to use. Accessing the input property earlier will cause an error.

Input and Output Properties

While the component itself works it is not very useful yet. There is no way to set its value or get notified when the user picks a date. Let’s fix that.

import { Input, Output, EventEmitter, ViewChild, ElementRef, AfterViewInit, Component } from '@angular/core';

declare var jQuery: any;

@Component({
  selector: 'my-datepicker',
  template: `<input #input type="text">`
})
export class DatePickerComponent implements AfterViewInit {
  @Input() value = '';
  @Output() dateChange = new EventEmitter();

  @ViewChild('input') input: ElementRef;

  ngAfterViewInit() {
    jQuery(this.input.nativeElement).datepicker({
      onSelect: (value) => {
        this.value = value;

        this.dateChange.next(value);
      }
    })
    .datepicker('setDate', this.value);
  }
}

Our component has a value property that allows us to set the current date from code. It also emits a dateChange event when the user selects a date.

Here how to use the new property and event (plunkr):

@Component({
  selector: 'my-app',
  template: `
    <div>
     <my-datepicker [value]="date" (dateChange)="onDateChange($event)"></my-datepicker>
     <div>Date: {{ date }}</div>
    </div>
  `,
})
export class AppComponent {
  date = "11/13/2016";

  onDateChange(date) {
    this.date = date;
  }
}

Form support

Still something is off. Our component does not support forms. We should implement the ControlValueAccessor interface to fix that.

import { forwardRef, Input, Output, EventEmitter, ViewChild, ElementRef, AfterViewInit, Component } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";

const DATE_PICKER_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DatePickerComponent),
  multi: true
};

declare var jQuery: any;

@Component({
  selector: 'my-datepicker',
  template: `<input #input type="text">`,
  providers: [DATE_PICKER_VALUE_ACCESSOR]
})
export class DatePickerComponent implements AfterViewInit, ControlValueAccessor {
  private onTouched = () => { };
  private onChange: (value: string) => void = () => { };

  @Input() value = '';

  writeValue(date: string) {
    this.value = date;

    jQuery(this.input.nativeElement).datepicker('setDate', date);
  }

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

  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

  @Output() dateChange = new EventEmitter();

  @ViewChild('input') input: ElementRef;

  ngAfterViewInit() {
    jQuery(this.input.nativeElement).datepicker({
      onSelect: (value) => {
        this.value = value;

        this.onChange(value);

        this.onTouched();

        this.dateChange.next(value);
      }
    })
    .datepicker('setDate', this.value);
  }
}

We register a new provider to tell Angular that our DatePicker component implements the ControlValueAccessor methods.

The DatePicker implements a few new methods: writeValue, registerOnChange and registerOnTouched.

  • Angular calls writeValue to sync a component with its value. In our case we set the jQuery UI DatePicker date.
  • The registerOnChange method registers a callback which tells Angular that the component value changed.
  • The registerOnTouched method registers a callback which tells Angular that the user interacted with the component.

We invoke the onChange and onTouched callbacks whenever the user selects a new date.

Here is a sample form that uses the DatePicker component (plunkr).

@Component({
  selector: 'my-app',
  template: `
    <form>
     <my-datepicker name="date" [(ngModel)]="form.date"></my-datepicker>
     <div>form: {{ form | json }}</div>
    </form>
  `,
})
export class AppComponentComponent {
  form = {
    date: "11/13/2016"
  };
}

Setting DatePicker options

The jQuery UI DatePicker has a lot of configuration options. Let’s support all of them by implementing a new Input property.

@Component({
  selector: 'my-datepicker',
  template: `<input #input type="text">`
})
export class DatePickerComponent implements AfterViewInit, ControlValueAccessor, OnDestroy {
  @Input() options: any = {};

  /* snip */

  ngAfterViewInit() {
    jQuery(this.input.nativeElement).datepicker(Object.assign({}, this.options, {
      onSelect: (value) => {
        this.value = value;

        this.onChange(value);

        this.onTouched();

        this.dateChange.next(value);
      }
    }))
    .datepicker('setDate', this.value);
  }
}

We created a new options property and pass it to the jQuery UI DatePicker plugin. This allows us to pass configuration options like this (plunkr):

<my-datepicker [options]="{numberOfMonths: 2}"></my-datepicker>

Cleanup

There is one remaining thing. We should cleanup the resources claimed by the jQuery UI DatePicker to avoid memory leaks. The ngOnDestroy method is the perfect place to do that.

@Component({
  selector: 'my-datepicker',
  template: `<input #input type="text">`
})
export class DatePickerComponent implements AfterViewInit, ControlValueAccessor, OnDestroy {
  /* snip */

  ngOnDestroy() {
    jQuery(this.input.nativeElement).datepicker('destroy');
  }
}

Our component is now ready to use. It supports validation, two-way data-binding, reactive forms and benefits from all the features provided by the jQuery UI DatePicker.

Until next time!

by Atanas Korchev

Event Bubbling and Angular 2

Checkout Radzen Demo application and download latest Radzen!
Read more