Angular 4.x – Integrate a jQuery Plugin

I’m using a very old twitter bootstrap template which is using jQuery Colorbox plugin to handle image preview modal like lightbox did. It took me more or less 4 hours to figure it out given I’m fairly new to Angular 2/4.

AngularJS Directives

Coming from the AngularJS world (Angular 1.5.x), problems like jQuery plugin integration can easily be solved by using the so-called Angular Directives. In the Angular 2+ world, directives (and almost everything else) are replaced by components. Don’t be confused with Angular 2+ directives as they are different things and better focus your effort on Angular 2+ components.

Integrate jQuery

To integrate any jQuery plugins, you must integrate jQuery first. You can install it using npm but make sure you also install the jQuery typings for TypeScript.

npm install jquery --save
npm install --save-dev @types/jquery

Then add these to your .angular-cli.json under the scripts section:

"apps": [{
  ...
  "scripts": [
    "../node_modules/jquery/dist/jquery.min.js",
  ],
  ...
}]

In your component that uses the jQuery plugin, just add this to the top, just below the import statements.

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

// You add it here
declare var jQuery: any;

// So you can use it like this later:
jQuery(element).pluginName();

More on that later.

Colorbox Integration

Install Colorbox via npm:

npm install jquery-colorbox --save

Then add it to your .angular-cli.json:

"apps": [{
  ...
  "scripts": [
    "../node_modules/jquery/dist/jquery.min.js",
    "../node_modules/jquery-colorbox/jquery.colorbox-min.js"
  ],
  ...
}]

Note: It doesn’t include the CSS file since my template has a custom colorbox CSS file which I’ve added in the .angular-cli.json file as well under styles.

"apps": [{
  ...
  "styles": [
    ...
    "assets/css/colorbox.css",
    ...
  ],
  "scripts": [
    "../node_modules/jquery/dist/jquery.min.js",
    "../node_modules/jquery-colorbox/jquery.colorbox-min.js"
  ],
  ...
}]

Colorbox Component

My Colorbox component is used multiple times in multiple pages (which makes it perfect for reuse) and has different parameters depending on how it is used. There are two parameters needed: img – for the target image URL and rel – to group the images to provide previous/next navigation. Below is the HTML structure based on my template.

<div class="img-wrap">
  <a class="colorbox cboxElement" href="http://example.com/path/to/image.png">
    <div class="black-bg">
      <i class="icon-zoom-in"></i>
    </div>
    <img alt="" src="http://example.com/path/to/image.png">
  </a>
</div>

I can make the thumbnail use the thumbnail version, but I’m lazy so I just use the original image for both thumbnail and preview/target.

This HTML structure is in a portfolio page where each projects contains at least 3 images. When you click an image, you should be able to see the full preview of the image (Colorbox) and should be able to navigate/cycle through the images within the same project.

To create the component, we need to create 2 files: the html template and the component itself.

src/app/colorbox/colorbox.component.html:

<div class="img-wrap">
  <a #colorbox class="colorbox cboxElement" href="{{img}}">
    <div class="black-bg">
      <i class="icon-zoom-in"></i>
    </div>
    <img alt="" src="{{img}}">
  </a>
</div>

In the HTML template above, we just replace the image URLs with Angular variable from our component.

src/app/colorbox/colorbox.component.ts:

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

declare var jQuery: any;

@Component({
  selector: 'app-colorbox',
  templateUrl: './colorbox.component.html'
})

export class ColorboxComponent implements AfterViewInit {
  @ViewChild('colorbox') colorbox: ElementRef;

  @Input() img = '';
  @Input() rel = 'gal';

  ngAfterViewInit() {
    jQuery(this.colorbox.nativeElement).colorbox({
      rel: this.rel,
      retinaImage: true,
      opacity: 1,
      current: false,
      maxWidth: '95%',
      maxHeight: '95%'
    });
  }
}

In our component, notice that we declare the jQuery variable to make it available inside the component. Then we reference the template file.

The component can be used using this snippet:

<app-colorbox [img]="http://example.com/path/to/image.png" [rel]="group_name"></app-colorbox>

Other parts are described below:

  • colorbox ViewChild – allows the element to access properties and methods of our component inside the template
  • img Input – allows passing parameter into the component when used, ie: passing the target image URL
  • ref Input – allows passing parameter into the component when used, ie: passing the group name
  • ngAfterViewInit() – allows adding logic when the component is rendered, ie: initializing the colorbox element

There should be a Destroy callback here but I can’t find a Destroy method for colorbox, so I don’t add it. This can be a source of memory leaks. Be sure to have the destroy callback when your plugin provides them to cleanup the element when it is destroyed.

Integrating the Colorbox Component

I have this Projects Component where the list of previews and current projects are listed. We will use the Colorbox Component here.

src/app/projects/projects.component.html:

<h2>Projects</h2>
<div class="thumbnails">
  <div class="span12" *ngFor="let project of projects">
    <div class="thumbnail">
      <h3>{{project.title}}</h3>
      <p *ngFor="let description of project.descriptions">{{description}}</p>
      <div class="row-fluid">
        <div class="span2" *ngFor="let img of project.images">
          <app-colorbox [img]="'/assets/images/portfolio/' + img" [rel]="project.name"></app-colorbox>
        </div>
      </div>
    </div>
  </div>
</div>

That’s a lot of code there, but for people coming from the AngularJS/1.x world, some syntax are familiar although a bit different.

  • *ngFor for projects – is iterating through an array of objects defining the project details
  • *ngFor for images – another iterator that iterates through a list of images
  • app-colorbox – that’s our component there, and notice that we pass the img and rel from the project info

src/app/projects/projects.component.ts:

import { Component } from '@angular/core';
import { PROJECTS } from './project-list';

@Component({
  selector: 'app-projects',
  templateUrl: './projects.component.html'
})

export class ProjectsComponent {
  projects = PROJECTS;
}

In our component above, we just define the template and a single property called projects which is a list of project definitions. I just like to separate that messy file outside our component. Below is the sample content of the project-list.ts.

src/app/projects/project-list.ts:

export const PROJECTS = [
  {
    name: 'project1',
    title: 'Project 1 Title',
    descriptions: [
      'Description 1',
      'Description 2'
    ],
    images: [
      'project1/img1.png',
      'project1/img2.png',
      'project1/img3.png'
    ]
  },
  {
    name: 'project2',
    title: 'Project 2 Title',
    descriptions: [
      'Description 1',
      'Description 2'
    ],
    images: [
      'project1/img1.png',
      'project1/img2.png',
      'project1/img3.png'
    ]
  }
];

Make Colorbox Component Available Anywhere

Notice that we didn’t import the ColorboxComponent anywhere yet, even in the ProjectsComponent. We need to add it into our AppModule declaration.

src/app/app.modules.ts:

import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { ProjectsComponent } from './projects/projects.component';
import { ColorboxComponent } from './colorbox/colorbox.component';
// Other components...
import { AppRoutingModule } from './app-routing.module';

// Other imports...

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    ProjectsComponent,
    // Other components...
    ColorboxComponent
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }

Finally

Now, we are ready to view our ColorboxComponent in action. Restart the DEV server (if you are using one) and see if it works.

Bonus: DEV server and deployment

I just used the Angular CLI for most of the part, with some NPM combination.

  • npm start --port 4200 – to start the DEV server using port 4200
  • ng build --prod --aot – to build the project ready to deploy to production

For simple use cases, you can just run the ng build --prod --aot in production then point your web server root to the path of dist. You don’t have to worry about CSS/JS being cached because the build command will create new filenames for these files, thus, avoiding hitting cache on new deployments.

This entry was posted in Angular and tagged , . Bookmark the permalink.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *