Using a JQuery Plug-in in a mean application through a directive and TypeScript Interfaces
There are loads of interesting Jquery
libs out there and not all of them have their corresponding Angular
modules offered. Since JQuery
has been out for a longer time, you are going to find cool JQuery
plugins with no equivalent as Angular
modules. It is advised to avoid explicitly using JQuery
in Angular
since the later was introduced to overcome the unorganized code resulting from the use of JQuery
; nevertheless, Angular
itself relies on JQuery
and hence can by default handle what is offered through JQuery
.
The problem remains that one cannot just afford to create Angular
modules for the JQuery
plugins they might be interested in, each and every time they need them. I faced this problem when I attempted to use CubePortfolio
in my MEAN Application; the JQuery
plugin works great but did not have an Angular
module. My solution was to use the plugin as an Angular
directive. I would give the data to the directive and let the directive display my data by applying the plugin magic on the data.
What I did first was to make sure that my JQuery
and CubePortfolio
scripts were imported and available in my single-page. I was then introduced to the concept of interfaces in Typescript
; one may declare within the JQuery
Typescript
interface the CubePortfolio
methods that will be used in our directive; these methods shall be the methods offered by the plugin which are declared in the interface. Please note the use of the any identifier to refer to the type of argument the methods accept.
//_directives/cube.portfolio.d.ts interface JQuery { cubeportfolio(options?:any):JQuery; cubeportfolio(type?:any, html?: any):JQuery; }
In my setting, the directive needs to receive snaps that will be formatted by my cubePortfolio plugin in a very fashionable way.
//_directives/cube.portfolio.component.ts import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, Input } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Snap } from "../_models/snap"; declare var $:JQueryStatic; /// <reference path="cube.portfolio.d.ts" /> @Component({ moduleId: module.id, selector: 'cubeportfolio', templateUrl: 'cube.portfolio.component.html' }) export class CubePortfolioComponent implements OnInit, AfterViewInit{ @ViewChild('gridSnapsContainer') el:ElementRef; cubePortfolioInited : boolean = false; private _snaps = new BehaviorSubject<Snap[]>([]); snap : Snap; selectedSnapId : string; @Input() set snaps(value) { // set the latest value for _data BehaviorSubject this._snaps.next(value); }; get snaps() { // get the latest value from _data BehaviorSubject return this._snaps.getValue(); } compSnaps : Snap []; ngOnInit() { this._snaps .subscribe (snaps => { this.compSnaps = snaps; this.applyCubePortfolio(); }); } applyCubePortfolio(){ var options = { layoutMode: 'grid', rewindNav: true, scrollByPage: false, mediaQueries: [{ width: 1100, cols: 3 }, { width: 800, cols: 3 }, { width: 500, cols: 2 }, { width: 320, cols: 1 }], defaultFilter: '*', animationType: 'rotateSides', gapHorizontal: 10, gapVertical: 10, gridAdjustment: 'responsive', caption: 'overlayBottomPush', displayType: 'sequentially', displayTypeSpeed: 100, // lightbox lightboxDelegate: '.cbp-lightbox', lightboxGallery: true, lightboxTitleSrc: 'data-title', lightboxCounter: '<div class="cbp-popup-lightbox-counter">{{current}} of {{total}}</div>', // singlePage popup singlePageDelegate: '.cbp-singlePage', singlePageDeeplinking: true, singlePageStickyNavigation: true, singlePageCounter: '<div class="cbp-popup-singlePage-counter">{{current}} of {{total}}</div>', singlePageCallback: function(url : any, element : any) { // to update singlePage content use the following method: this.updateSinglePage(yourContent) }, // singlePageInline singlePageInlineDelegate: '.cbp-singlePageInline', singlePageInlinePosition: 'below', singlePageInlineInFocus: true, singlePageInlineCallback: function(url : any, element : any) { // to update singlePageInline content use the following method: this.updateSinglePageInline(yourContent) var t = this; // Update with the service $.ajax({ url: "api/snap/"+url, type: 'GET', dataType: 'html', timeout: 5000 }) .done(function(result) { t.updateSinglePageInline(result); }) .fail(function() { t.updateSinglePageInline("Error! Please refresh the page!"); }); } }; if(this.compSnaps != null){ if(this.cubePortfolioInited){ /** * Start by a destroy */ $(this.el.nativeElement).cubeportfolio('destroy'); } setTimeout(() => { $(this.el.nativeElement).cubeportfolio(options); this.cubePortfolioInited = true; }, 2000); // $(this.el.nativeElement).cubeportfolio('appendItems', htmlContent); } } ngAfterViewInit() { } }
Then in my component I will retrieve the snaps I want to display through the getSnaps
method and will, later on, pass them to the cubPortfolio directive within the component view template.
//snaps/snaps.component.ts import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { Location } from "@angular/common"; import { Observable } from "rxjs"; import {Subject} from 'rxjs/Subject'; import { Snap } from "../_models/snap"; import { SnapService } from "../_services/snap.service"; @Component({ moduleId: module.id, selector : "lalifeapp", templateUrl : 'snaps.component.html' }) export class SnapsComponent implements OnInit{ snaps : Subject <Snap[]> = new Subject(); snaps$ = this.snaps.asObservable(); compSnaps : Snap []; constructor( private snapService : SnapService, private router : Router, private location : Location ){}; getSnaps(): void{ this.snapService.getSnaps() .then(snaps => { this.compSnaps = snaps; this.snaps.next(snaps); }); } ngOnInit(): void { this.getSnaps(); } deleteSnap(id : string): void{ this.snapService.deleteSnap(id) .then(() => { // this.goBack(); this.router.navigateByUrl("/"); }); } goBack() : void { this.location.back(); } }
The corresponding view shall pass the snaps to the directive:
//snaps/snaps.component.html <cubeportfolio [snaps]='compSnaps'></cubeportfolio>