Radzen blog

Rapid application development for Angular

jQuery plugins and Angular 2

Published on by

In this blog post I will show you how to use a jQuery plugin in an Angular 2 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="http://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="http://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="http://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 DatePicker 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 DatePicker 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 App {
  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(() => DatePicker),
  multi: true
};

declare var jQuery: any;

@Component({
  selector: 'my-datepicker',
  template: `<input #input type="text">`,
  providers: [DATE_PICKER_VALUE_ACCESSOR]
})
export class DatePicker 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 App {
  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 DatePicker 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 DatePicker 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!

radzen.png

Radzen is a rapid application development solution for Angular. Create modern web apps without writing HTML, CSS or JavaScript. Connect them to your REST API, OData service or MS SQL Server.

Try it for free now!