Create a Photo Gallery with Angular 4

In this angular 4 tutorial, we are going to create a photo gallery using Angular 4 and Cloudinary. Cloudinary is a service that allows you to seamlessly manage your website’s images in the cloud – image upload, cloud storage, image manipulation, image API and fast CDN.

Live Demo


We will be using Angular IDE by Webclipse, so if you do not have it installed go ahead and download it from here. Now, Open Angular IDE and create a new project ImageGallery.

Overview

The ImageGallery will have to views, a manager view to upload new images and a gallery view to view uploaded images. Let us create two components ManagerComponet and GalleyComponent, you create a new component by right clicking on the app folder in project explorer; hover on New; then click Component.

angular ide
Let us setup a navigation section to switch between the gallery view and the manager view.

First, we include bootstrap CSS in the head section of our index.html file.


<!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <!-- Optional theme --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

The manager view and gallery view can be thought of as two separate web pages, and as we know, different web pages have different URLs. It is common in web development to think of each unique URL in our application as a route. Unlike React and Vue (which focus on the V in MVC), Angular includes a router.

The Angular Router enables navigation from one view to the next as users perform application tasks. Let us proceed to use the Angular Router. First, we update app.component.html, the new content is as follows

< base href="/">
< div class="navbar navbar-default">
    < div class="container">
        < div class="navbar-header">
            <a class="navbar-brand">{{title}}</a>
        </ div>

        < ul class="nav navbar-nav navbar-right">
            <li routerLinkActive="active"><a routerLink="/gallery">Gallery</a></li>
            <li routerLinkActive="active"><a routerLink="/manager">Manager</a></li>
        </ ul>
    </ div>
</ div>

< div class="container">
    <router-outlet></router-outlet>
</ div>

The Angular Router docs says most routing applications should add a base element to the index.html as the first child in the tag to tell the router how to compose navigation URLs.

Now we need to update app.module.ts

import { RouterModule, Routes } from [email protected]/router'; // add to import statements at top of file

/** Add below import statements **/
const appRoutes: Routes = [
  {path: 'gallery', component: GalleryComponent},
  {path: 'manager', component: ManagerComponent},
  {path: '', redirectTo: '/gallery', pathMatch: 'full'}
];

// update the imports property of Apps Module
  ...
  imports: [
    RouterModule.forRoot(appRoutes),
    BrowserModule
  ]
  ...

appRoutes is the configuration for Angular Routes in our application, what we have done is
* When our URL matches http://DOMAIN_NAME/gallery i.e. when the path is gallery angular displays the GalleryComponent
* When our URL matches http://DOMAIN_NAME/manager i.e. when the path is manager angular displays the ManagerComponent
* Redirect to http://DOMAIN_NAME/gallery when the path is http://DOMAIN_NAME

Now let’s proceed to develop the other components.

Recommended :  Cleanups in symfony 2

Manager

On the manager, we want to be able to do the following
* Have a drop area for images dragged from our desktop
* Upload images dropped in the area to Cloudinary
* Store the image data from cloudinary to Local storage
* Preview thumbnails of uploaded images

In order to do the steps above easily we will be installing a few npm packages, so we execute the following lines of code in our cli

npm install @cloudinary/angular-4.x cloudinary-core ng2-file-upload lokijs --save

cloudinary-angular and cloudinary-core are Angular libraries that allow us to easily upload to and display images from Cloudinary, ng2-file-upload allows us to easily create file drop zone and file upload in angular while lokijs is a lightweight javascript in-memory database that we can persist to local storage.
Now we need to update app.module.ts


// add to import statements at top of file import {CloudinaryModule, CloudinaryConfiguration, provideCloudinary} from [email protected]/angular-4.x'; import * as cloudinary from 'cloudinary-core'; import { FileUploadModule } from 'ng2-file-upload'; // update imports in the @NgModule decorator imports: [ CloudinaryModule.forRoot(cloudinary, CloudinarySettings), FileUploadModule, RouterModule.forRoot(appRoutes), BrowserModule ]

Please do the following also:
* Create a cloudinary account
* Create an upload preset on cloudinary to allow unsigned uploading directly from the browser
* make a copy of settings.ts.sample and rename as settings.ts
* update the variables in settings.ts

Now we are ready to update ManagerComponent, the content of manager.component.html is updated to:

< h3>Upload Image</ h3>
< div class="row">
    < div class="col-md-3">
        < h3>Select files</ h3>
        < div ng2FileDrop [ngClass]="{'nv-file-over': hasBaseDropZoneOver}"
            (fileOver)="fileOverBase($event)" [uploader]="uploader"
            class="well my-drop-zone">Drop image here</ div>
    </ div>

    < div class="col-md-9" style="margin-bottom: 40px">
        < h3>Upload queue</ h3>
        < p>Queue length: {{ uploader?.queue?.length }}</ p>
        < table class="table">
            < thead>
                < tr>
                    < th width="50%">Name</ th>
                    < th>Size</ th>
                    < th>Progress</ th>
                    < th>Status</ th>
                    < th>Actions</ th>
                </ tr>
            </ thead>
            < tbody>
                < tr *ngFor="let item of uploader.queue">
                    < td>< strong>{{ item?.file?.name }}</ strong></ td>
                    < td *ngIf="uploader.isHTML5" nowrap>{{
                        item?.file?.size/1024/1024 | number:'.2' }} MB</ td>
                    < td *ngIf="uploader.isHTML5">
                        < div class="progress" style="margin-bottom: 0;">
                            < div class="progress-bar"
                                [ngStyle]="{ 'width': item.progress + '%' }"></ div>
                        </ div>
                    </ td>
                    < td class="text-center">< span *ngIf="item.isSuccess">< i
                            class="glyphicon glyphicon-ok"></ i></ span> < span *ngIf="item.isCancel">< i
                            class="glyphicon glyphicon-ban-circle"></ i></ span> < span
                        *ngIf="item.isError">< i class="glyphicon glyphicon-remove"></ i></ span>
                    </ td>
                    < td nowrap>
                        < button type="button" class="btn btn-success btn-xs"
                            (click)="item.upload()"
                            [disabled]="item.isReady || item.isUploading || item.isSuccess">
                            < span class="glyphicon glyphicon-upload"></ span> Upload
                        </ button>
                        < button type="button" class="btn btn-warning btn-xs"
                            (click)="item.cancel()" [disabled]="!item.isUploading">
                            < span class="glyphicon glyphicon-ban-circle"></ span> Cancel
                        </ button>
                        < button type="button" class="btn btn-danger btn-xs"
                            (click)="item.remove()">
                            < span class="glyphicon glyphicon-trash"></ span> Remove
                        </ button>
                    </ td>
                </ tr>
            </ tbody>
        </ table>
        < div>
            < div>
                Queue progress:
                < div class="progress" style="">
                    < div class="progress-bar"
                        [ngStyle]="{ 'width': uploader.progress + '%' }"></div>
                </ div>
            </ div>
            < button type="button" class="btn btn-success btn-s"
                (click)="uploader.uploadAll()"
                [disabled]="!uploader.getNotUploadedItems().length">
                < span class="glyphicon glyphicon-upload"></ span> Upload all
            </ button>
            < button type="button" class="btn btn-warning btn-s"
                (click)="uploader.cancelAll()" [disabled]="!uploader.isUploading">
                < span class="glyphicon glyphicon-ban-circle"></ span> Cancel all
            </ button>
            < button type="button" class="btn btn-danger btn-s"
                (click)="uploader.clearQueue()" [disabled]="!uploader.queue.length">
                < span class="glyphicon glyphicon-trash"></ span> Remove all
            </ button>
        </ div>
    </ div>
</ div>

< h3>Your Images</ h3>
< div>
    < p *ngIf="!imageDataArray">Add images by dragging and dropping
        upload region above</ p>

    < cl-image *ngFor="let data of imageDataArray"
        [public-id]="data.public_id" class="imgThumbnail" format="jpg">
    < cl-transformation height="150" width="150" crop="fill" radius="20"></ cl-transformation>
    </ cl-image>
</ div>

manager.component.css

.my-drop-zone { border: dotted 3px lightgray; }
.nv-file-over { border: dotted 3px red; } /* Default class applied to drop zones on over */

.imgThumbnail{
    padding: 4px;
}

manager.component.ts


import { Component, OnInit } from [email protected]/core'; import { FileUploader, FileUploaderOptions, ParsedResponseHeaders } from 'ng2-file-upload'; import { Cloudinary } from [email protected]/angular-4.x'; import { DB } from '../database'; @Component({ selector: 'app-manager', templateUrl: './manager.component.html', styleUrls: ['./manager.component.css'] }) export class ManagerComponent implements OnInit { public uploader: FileUploader; public hasBaseDropZoneOver = false; private title: string; public imageDataArray; constructor(private cloudinary: Cloudinary) { this.title = ''; } ngOnInit() { this.loadDB(); this.loadUploadedImages(); const uploaderOptions: FileUploaderOptions = { url: `https://api.cloudinary.com/v1_1/${this.cloudinary.config().cloud_name}/image/upload`, // Upload files automatically upon addition to upload queue autoUpload: true, // Use xhrTransport in favor of iframeTransport isHTML5: true, // Calculate progress independently for each uploaded file removeAfterUpload: true, // XHR request headers headers: [ { name: 'X-Requested-With', value: 'XMLHttpRequest' } ] }; const upsertResponse = fileItem =&gt; { // Check if HTTP request was successful if (fileItem.status !== 200) { console.log('Upload to cloudinary Failed'); console.log(fileItem); return false; } let imageCollection = DB.getCollection('imagegallery'); if (!imageCollection) { imageCollection = DB.addCollection('imagegallery') } imageCollection.insert(fileItem.data); const that = this; DB.saveDatabase(function(saveErr) { if (saveErr) { console.log('error : ' + saveErr); } else { that.loadUploadedImages(); } }); } this.uploader = new FileUploader(uploaderOptions); this.uploader.onBuildItemForm = (fileItem: any, form: FormData): any =&gt; { // Add Cloudinary's unsigned upload preset to the upload form form.append('upload_preset', this.cloudinary.config().upload_preset); // Add built-in and custom tags for displaying the uploaded photo in the list let tags = 'angularimagegallery'; if (this.title) { form.append('context', `photo=${this.title}`); tags = `angularimagegallery,${this.title}`; } form.append('tags', tags); form.append('file', fileItem); // Use default "withCredentials" value for CORS requests fileItem.withCredentials = false; return { fileItem, form }; } // Update model on completion of uploading a file this.uploader.onCompleteItem = (item: any, response: string, status: number, headers: ParsedResponseHeaders) =&gt; upsertResponse( { file: item.file, status, data: JSON.parse(response) } ); } loadDB(): void { DB.loadDatabase({}, function(err) { if (err) { console.log(); } else { console.log('db loaded'); } }); } loadUploadedImages(): void { const imageCollection = DB.getCollection('imagegallery'); if (imageCollection) { this.imageDataArray = imageCollection.find(); } } public fileOverBase(e: any): void { this.hasBaseDropZoneOver = e; } }

Gallery

We update GalleryComponent as follows
gallery.component.html

< div class="row">
    < div class="thumbs col-md-3">
        < div *ngIf="!activeImage">Please add some photos to your album in the <a routerLink="/manager">Manager section</a> </ div>
        < cl-image *ngFor="let data of imageDataArray; let i=index"
            [public-id]="data.public_id" class="imgThumnail" format="jpg"
            (click)="updateActiveImage(i)"> < cl-transformation
            height="150" width="150" crop="fill" radius="20"></ cl-transformation>
        </ cl-image>
    </ div>
    < div class="col-md-9 screen">
        < cl-image *ngIf="activeImage" [public-id]="activeImage.public_id" class="imgThumbnail"
            format="jpg"> < cl-transformation height="600" crop="fill"
            radius="20"></ cl-transformation> </ cl-image>
    </ div>
</ div>

gallery.component.css

.thumbs{
    height: 90vh;
    text-align: center;
}

.screen {
    text-align: center;
}

gallery.component.ts

import { Component, OnInit } from [email protected]/core';

import { DB } from '../database';

@Component({
  selector: 'app-gallery',
  templateUrl: './gallery.component.html',
  styleUrls: ['./gallery.component.css']
})
export class GalleryComponent implements OnInit {
  public imageDataArray;
  public activeImage;
  constructor() { }

  ngOnInit() {
    this.loadDB();
    this.loadUploadedImages();
  }

  loadDB(): void {
    DB.loadDatabase({}, function(err) {
      if (err) {
        console.log();
      } else {
        console.log('db loaded');
      }
    });
  }

  loadUploadedImages(): void {
    const imageCollection = DB.getCollection('imagegallery');

    if (imageCollection) {
      this.imageDataArray = imageCollection.find();
      if (this.imageDataArray.length > 0) {
        this.activeImage = this.imageDataArray[0];
      }
    }
  }

  updateActiveImage(index): void {
    this.activeImage = this.imageDataArray[index];
  }
}

Our Photo gallery is now Ready

ImageGallery


About the author

Gbekeloluwa Simeon Olufotebi

I'm Gbekeloluwa Olufotebi and I am a product manager. I also design user interfaces and code often. I love python and I am adept at Fullstack Javascript. Vue is my favorite frontend framework but I also contribute to ember, react and angular projects.

Pin It on Pinterest

Shares