Building an image-slider element using Angular

1. Introduction

By guest author Aayush Arora

Angular Elements are Angular Components packaged as custom elements. They are currently supported by Chrome, Opera and Safari and are available in other browsers through polyfills. These elements can make use of the whole Angular Infrastructure with common Angular Interface and Change Detection Strategy. Once registered, these elements can be used within the browser.

This codelab will walk you through creating your own image-slider angular component, and then will help you to transform it into an angular element so that it can work outside the Angular Framework.

What you will build

In this codelab, you're going to build a image-slider element using angular. Your element wi:

  • work like a HTML element in the browser
  • Able to plugged in any framework that talks to the DOM.

What you'll learn

  • How to make an image-slider custom component
  • How to transform the image-slider custom component to custom element
  • How to package the component so that it works inside the browser

What you'll need

This codelab is focused on Angular Elements. Non-relevant concepts and code blocks are glossed over and are provided for you to simply copy and paste.

2. Getting set up

Download the Code

Click the following link to download all the code for this codelab:

Unpack the downloaded zip file. This will unpack a root folder (angular-element-codelab-master), which contains

two folders (image-slider) and (image-slider-finished). We'll be doing all our coding work in a directory called image-slider.

Running the project

To run the project, you need to run the command ( ng-serve ) from the root directory ( image-slider ).

Once the app is bootstrapped, you will be able to see this:

19ffd082e2f024a5.png

3. Making an Image-Slider custom component ?

How to create an image slider?

For this image slider, bind buttons using angular click binding. We will create an array of objects containing images, alt tags, links etc. We will place these images one below each other in a container and translate the container on click.

We are going to create an image-slider component and then will transform that into angular-element.

  • Container for images and titles.
  • An array containing the data
  • Template to bind the data

4. Implement the image-slider component

There are multiple ways to get started with any project, in this case, to keep our project as simple as possible and concentrate on Angular Elements, we've provided you with basic code along with the css.

Creating an array and data service

Remember, the sliderArray will contain:

  • An img key for the image URL in the slider
  • An alt tag to provide alt for the image
  • A text to provide the description about the image

The data.json file that is already in your src/assets directory should look something like this.

sliderArray = [
 {img: 'http://bloquo.cc/img/works/1.jpg', alt: '', text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/2.jpg', alt: '',  text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/3.jpg', alt: '', text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/4.jpg', alt: '',  text: '365 Days Of weddings a year'},
 {img: 'http://bloquo.cc/img/works/5.jpg', alt: '', text: '365 Days Of weddings a year'}
];

We need to fetch this data in our component using a service. In the file data.service.ts, we will write a getData() method using the httpClient module from @angular/common/http which will fetch the data from the array we have created above.

 
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'

const URL = '../assets/data.json';

@Injectable({
 providedIn: 'root'
})
export class DataService {

 constructor(private http: HttpClient) {}

 getData() {
   return this.http.get(URL)
 }
}

Fetching the data from the data service

We need to import our service inside the component and then we can subscribe to the observable to get the object from data.json

We need to perform three steps for this:

  • Initialising a component array
  • Subscribing to the Observable returned by getData() function
  • Create an interface Result for type-checking the data after subscribing to the observable.
  • Assign the data to the component array.

Initialising Component Array

We will declare and initialise the component array inside slider.component.ts that is an array of objects:

To declare:

sliderArray: object[];

To initialize:

constructor(private data: DataService) {
 this.sliderArray = [];
}

Next, we need to import and initialize our service inside the constructor

constructor(private data: DataService) {}

Now, we are ready to use our service, and to call our service methods.

Getting data from Data Service

To get the data out from the service, we will call the getData() method and subscribe to the observable that it will return, we will also create an interface Result,so that we can type check that we are getting the correct data.

We will do this inside ngOnInit method:

this.data.getData().subscribe((result: Result)=>{
})

Assigning data to Component Array

At the end, we will assign the data to the component array:

this.data.getData().subscribe((result: Result)=>{  
  this.sliderArray = result.sliderArray;
})

Once we get the data inside our component's array, we can then bind our template with this data.

In the slider.component.html, we already have a HTML template. Our next step is to bind this template with the sliderArray.

5. Binding the data with the template

We will bind the data with the template using *ngFor Directive and finally we will add transformations in the template to get the slider working.

This contains three steps:

  • Binding sliderArray to the template
  • Adding Event Binding for slider buttons
  • Adding css transforms using ngStyle and ngClass

Binding slideArray to the Component

We have a container containing an img-container, a text-container, and a slider.

We will bind the data in all the three containers using *ngFor directive

<div class="container">
 <div class="img-container" *ngFor="let i of sliderArray; let select = index;">
   <img src="{{i.img}}" alt="{{i.alt}}" >
 </div>

 <div>
   <div class="text-container">
     <div class="page-text" *ngFor="let i of sliderArray;let select = index;">
       <h3>{{i.text}}</h3>
     </div>
   </div>
 </div>

</div>

<div class="slider">
 <div class="slide-button-parent-container" *ngFor="let i of sliderArray; let x =index">
    <div class="select-box">
     <div class="slide-button">
     </div>
    </div>
 </div>
</div>

Event Binding to slideArray

Once, the data is binded , we will bind the click event to every slide-button using angular click binding. We will create a function called selected(x)where x is the index of the array.

selected(x) {
 this.downSelected(x);
 this.selectedIndex = x;
}

downSelected(i) {
  this.transform =  100 - (i) * 50;
  this.selectedIndex = this.selectedIndex + 1;
  if(this.selectedIndex > 4) {
    this.selectedIndex = 0;
  }
}

Points to remember here:

  • The downselected function decreases the value of transform property fifty times the index passed on click of selected function.
  • This logic translates the text container to 100%, 50%, -50%, -100% resulting in four different states.

Adding CSS transforms using ngStyle & ngClass

Initially we set all images, at an opacity of zero, we add a class selected using ngClass directive when the selected index becomes equal to the image index. This selected class adds an opacity of one to the image making the image visible to the user.

<div class="img-container"  *ngFor="let i of sliderArray; let select = index;"
      [ngClass]="{'selected': select == selectedIndex}">
</div>

After this, we will translate the text-container according to the transform value computed using select() function.

<div [ngStyle]="{'transform': 'translateY('+ transform + '%' +')', 'transition': '.8s'}">
</div>

After you have performed all these steps, you can find out the final code as provided below:

<div class="container">
 <div class="img-container"  *ngFor="let i of sliderArray; let select = index;"
      [ngClass]="{'selected': select == selectedIndex}">
   <img src="{{i.img}}" alt="{{i.alt}}" >
 </div>

 <!--</div>-->
 <div [ngStyle]="{'transform': 'translateY('+ transform + '%' +')', 'transition': '.8s'}">
   <div class="text-container">
     <div class="page-text" *ngFor="let i of sliderArray;let select = index;" [ngClass]="{'selected': select == selectedIndex}">
       <h3>{{i.text}}</h3>
     </div>
   </div>
 </div>

</div>

<div class="slider">
 <div class="slide-button-parent-container"  *ngFor="let i of sliderArray; let x =index" (click)="selected(x)" >
    <div class="select-box">
     <div   class="slide-button" [ngClass]="{'slide-button-select': x == selectedIndex}" >
     </div>
    </div>
 </div>
</div>

6. Transforming the component into the angular element

This procedure consists of five steps:

  • Using Shadow DOM for angular Element
  • Making use of entryComponents
  • Importing and using CreateCustomElement module from @angular/elements
  • Defining our custom-element
  • Running ngDoBootstrap Method

Using Shadow DOM for angular element

Now we have our image-slider running, we just need to make it an Angular Element.

The fun part is that, there is only a minor change for making the component DOM, a shadow DOM.

We need to import ViewEncapsulation module and have to use the ShadowDom method from it.

@Component({
 selector: 'app-slider',
 templateUrl: './slider.component.html',
 styleUrls: ['./slider.component.css'],
 encapsulation: ViewEncapsulation.ShadowDom
})

Making use of entryComponents

The entry Component is a component that angular loads imperatively. You specify an entry component by bootstrapping it in an NgModule.

Here, we will specify our SliderComponent in the entryComponents array inside @NgModule

@NgModule({
 declarations: [
   SliderComponent
 ],
 imports: [
   BrowserModule,
   HttpClientModule
 ]
})

Importing and using createCustomElement Module

Here, we need to use the createCustomElement Module from @angular/elements. You need to use the SliderComponent, as a parameter to the createCustomElement function. After that, we need to register the slider in the DOM.

import { createCustomElement } from '@angular/elements';

export class AppModule {
 constructor(private injector: Injector) {
   const slider = createCustomElement(SliderComponent, { injector });
    }
}

To register the slider as a DOM element, we will define it using customElements.define method.

customElements.define('motley-slider', slider);

Finally, we have to bootstrap this custom-element using the ngDoBootstrap() method. The complete code will look like this:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { SliderComponent } from './slider/slider.component';
import { HttpClientModule} from "@angular/common/http";

@NgModule({
 declarations: [
   SliderComponent
 ],
 imports: [
   BrowserModule,
   HttpClientModule
 ]
})
export class AppModule {
 constructor(private injector: Injector) {
   const slider = createCustomElement(SliderComponent, { injector });
   customElements.define('motley-slider', slider);
 }

 ngDoBootstrap() {}

}

Packaging the Angular Element

We need to modify package.json with our new commands, we will modify the script object inside the package.json file.

Let's check our modified script object:

"scripts": {
 "ng": "ng",
 "start": "ng serve",
 "build": "ng build --prod --output-hashing=none",
 "package": "cat dist/my-app/{runtime,polyfills,scripts,main}.js | gzip > elements.js.gz",
 "serve": "http-server",
 "test": "ng test",
 "lint": "ng lint",
 "e2e": "ng e2e"
}

Now, we can run the command ng build & ng package and finally we will run ng serve to serve the dist/ folder generated using the build command. Also, we can use the gzip obtained from the ng package command, extract it and can publish it as an npm module.