Checkout Radzen Demos 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 theinput
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!