Photo editing apps play a significant role in our digital lives. Over 4 in 10 smartphone users regularly use photo editing apps, enhancing their photos and selfies before sharing them online. Whether you’re building a personal website, an e-commerce platform, or a content-rich application, image editing capabilities play a crucial role. That’s where an image editor comes into play—a versatile component that empowers developers to integrate image editing features into their Angular applications.
In this article, we’ll create an image editor in Angular. From resizing and rotating to adding attractive filters, this tool can simplify image manipulation while utilizing the robust capabilities of Cloudinary, a leading cloud-based media management platform.
In this article:
Features of Angular Image Editors
Angular Image Editors are powerful tools designed to integrate seamlessly into web applications, providing sophisticated features for manipulating images directly within the browser. Here’s a glimpse into what these editors can typically offer:
- Layer Management – Enables the addition, removal, and adjustment of multiple layers within an image, akin to professional-grade software.
- Undo/Redo Commands – Essential for a forgiving editing process, allowing users to experiment without fear of making irreversible mistakes.
- Real-time Previews – Offers an immediate visual feedback loop, ensuring adjustments are seen instantaneously.
- Customizable Filters and Effects – These editors let users easily enhance their images with basic brightness and contrast adjustments and more complex effects like blurs and color corrections.
- Annotation Tools – A suite of annotation options, including text addition, shapes, and drawing tools for detailed image markups.
- Placeholders/icons: Provides placeholder images/icons for intros, error states, or building out UI mockups.
- Responsive Design – Ensures the editor’s interface is accessible and user-friendly across all device sizes, from desktops to mobile phones.
- Integration Capabilities – Designed to fit smoothly into Angular applications, these editors can be easily plugged into existing projects, enhancing their functionality without disrupting workflow.
Leveraging these features, Angular Image Editors empower developers to provide end-users with a rich, interactive experience directly in their web applications. By offering various tools and options, they bridge the gap between professional image editing and web-based applications, meeting the growing demand for in-browser creativity and customization.
Creating An Angular Image Editor
For this tutorial, we will create our image editor using Cloudinary’s various image transformation features. If you haven’t already, you can sign up for free. For now, we will use our Cloudinary API credentials, so head over to Cloudinary and log in to your account.
Next, click the Programmable Media button on your Cloudinary Programmable Media Dashboard. Copy these credentials, as we’ll need them to connect to Cloudinary’s cloud:
With this, we can begin creating our Angular app.
Setting Up and Creating Our Angular Image Editor
Before we begin creating our image editor, we will need to install Angular. To do this, simply run the following command:
npm install -g @angular/cli@17
Next, open up a sample project directory and use the following command to create a project:
ng new image-editor
This will create a new image-editor directory and generate the initial project structure. Navigate to the image-editor directory and open up the project in your IDE. Here, we will begin by installing the Cloudinary Angular JS SDK using the following command:
npm i @cloudinary/url-gen @cloudinary/ng
With this, our setup is complete, and we can begin coding our image editor. We will start by opening up the app.component.ts
file in your project’s src/app
directory. Here, we will first add some imports that will allow us to access Cloudinary’s features:
import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterOutlet } from '@angular/router'; import { CloudinaryModule } from '@cloudinary/ng'; import { Cloudinary, CloudinaryImage } from '@cloudinary/url-gen'; import { byAngle } from '@cloudinary/url-gen/actions/rotate'; import { fill } from '@cloudinary/url-gen/actions/resize'; import { sepia, grayscale, cartoonify } from '@cloudinary/url-gen/actions/effect';
Next, we will add a simple component decorator and start defining our AppComponent class:
@Component({ selector: 'app-root', standalone: true, imports: [CommonModule, RouterOutlet, CloudinaryModule], templateUrl: './app.component.html', })
Now, we will begin by creating two variables, img!
, to store the Cloudinary image object, and imageId,
to store the public ID of our image. Then, we declare a Cloudinary instance and initialize our Cloudinary API:
export class AppComponent implements OnInit { img!: CloudinaryImage; imageId: string = 'cld-sample'; cld: Cloudinary; // Declare a Cloudinary instance constructor() { // Initialize Cloudinary configuration this.cld = new Cloudinary({ cloud: { cloudName: 'your_cloud_name', apiKey: 'your_api_key', apiSecret: 'your_api_secret', }, });
Next, we will use the ngOnInit()
hook to load a default image and resize it to a width and height of 300
and 200
, respectively:
ngOnInit() { // Load the default image and resize it this.img = this.cld.image(this.imageId); this.img = this.cld.image(this.imageId).resize(fill().width(300).height(200)); }
Now, we will create a loadImage()
function that takes the public ID of an image as a parameter and loads it onto the app.
loadImage(imageId: string) { this.imageId = imageId; // Update the imageId property this.img = this.cld.image(imageId); this.img = this.cld.image(this.imageId).resize(fill().width(300).height(200)); // Default dimensions of image }
Finally, we will define a transformImage()
function that will use the effects selected on our app to transform our loaded image. Here is what our complete app.component.ts
file looks like:
import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterOutlet } from '@angular/router'; import { CloudinaryModule } from '@cloudinary/ng'; import { Cloudinary, CloudinaryImage } from '@cloudinary/url-gen'; import { byAngle } from '@cloudinary/url-gen/actions/rotate'; import { fill } from '@cloudinary/url-gen/actions/resize'; import { sepia, grayscale, cartoonify } from '@cloudinary/url-gen/actions/effect'; @Component({ selector: 'app-root', standalone: true, imports: [CommonModule, RouterOutlet, CloudinaryModule], templateUrl: './app.component.html', }) export class AppComponent implements OnInit { img!: CloudinaryImage; imageId: string = 'cld-sample'; cld: Cloudinary; // Declare a Cloudinary instance constructor() { // Initialize Cloudinary configuration this.cld = new Cloudinary({ cloud: { cloudName: 'your_cloud_name', apiKey: 'your_api_key', apiSecret: 'your_api_secret', }, }); } ngOnInit() { // Load the default image and resize it this.img = this.cld.image(this.imageId); this.img = this.cld.image(this.imageId).resize(fill().width(300).height(200)); } loadImage(imageId: string) { this.imageId = imageId; // Update the imageId property this.img = this.cld.image(imageId); this.img = this.cld.image(this.imageId).resize(fill().width(300).height(200)); // Default dimensions of image } transformImage() { const selectedEffect = (<HTMLInputElement>document.querySelector('input[name="effect"]:checked')).value; const selectedRotation = parseInt((<HTMLInputElement>document.querySelector('input[name="rotate"]:checked')).value); const width = parseInt((<HTMLInputElement>document.querySelector('#imageWidth')).value); const height = parseInt((<HTMLInputElement>document.querySelector('#imageHeight')).value); switch (selectedEffect) { // Apply effect transformation case 'cartoonify': this.img = this.cld.image(this.imageId).effect(cartoonify()) break; case 'sepia': this.img = this.cld.image(this.imageId).effect(sepia()) break; case 'grayscale': this.img = this.cld.image(this.imageId).effect(grayscale()) break; case 'none': break; } if (selectedRotation !== 0) { // Apply rotation this.img = this.img.rotate(byAngle(selectedRotation)); } if (!isNaN(width) && !isNaN(height)) { // Resize the image this.img = this.img.resize(fill().width(width).height(height)); } } }
Now that our backend is complete, all we need to do is create a UI for our image editor. To do this, open up the app.component.html
file. Here, we will begin by linking Bootstrap for styling the layout and form elements:
<main class="main d-flex justify-content-center align-items-center flex-column"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <style> .image-form { margin: 10px; } fieldset { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } legend { font-weight: bold; } .form-check { margin-bottom: 5px; } </style>
Next, we define an input field for the Cloudinary image ID and its corresponding load button, allowing the user to enter the public ID of their Cloudinary image. Next, we use the advanced-image
custom component. This component displays the loaded image with transformations applied.
Additionally, we provide user control via two fieldsets with radio buttons. The first allows selecting an effect (sepia, cartoonify, grayscale, or none), while the second lets users choose a rotation angle (0°, 90°, or -90°). Finally, we offer two input fields for custom sizing for the desired width and height alongside a transform
button that triggers the transformImage()
function to apply the selections. Here’s what our complete HTML file looks like:
<main class="main d-flex justify-content-center align-items-center flex-column"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <style> .image-form { margin: 10px; } fieldset { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } legend { font-weight: bold; } .form-check { margin-bottom: 5px; } </style> <!-- Input field for Cloudinary image ID --> <div class="image-form"> <input class="m-2" type="text" placeholder="Enter Cloudinary image ID" #imageIdInput> <button class="btn btn-outline-secondary" type="button" (click)="loadImage(imageIdInput.value)">Load Image</button> </div> <!-- Display the loaded image --> <advanced-image [cldImg]="img"></advanced-image> <!-- Effect selection --> <fieldset> <legend>Effect</legend> <div role="group" aria-label="Basic radio toggle button group"> <div class="form-check"> <input type="radio" class="form-check-input" id="sepia" name="effect" value="sepia" autocomplete="off"> <label class="form-check-label" for="sepia">Sepia</label> </div> <div class="form-check"> <input type="radio" class="form-check-input" id="cartoonify" name="effect" value="cartoonify" autocomplete="off"> <label class="form-check-label" for="cartoonify">Cartoonify</label> </div> <div class="form-check"> <input type="radio" class="form-check-input" id="grayscale" name="effect" value="grayscale" autocomplete="off"> <label class="form-check-label" for="grayscale">Grayscale</label> </div> <div class="form-check"> <input type="radio" class="form-check-input" id="none" name="effect" value="none" autocomplete="off"> <label class="form-check-label" for="none">None</label> </div> </div> </fieldset> <!-- Rotation selection --> <fieldset> <legend>Rotate</legend> <div role="group" aria-label="Rotation radio toggle button group"> <div class="form-check"> <input type="radio" class="form-check-input" id="rotate0" name="rotate" value="0" autocomplete="off"> <label class="form-check-label" for="rotate0">0°</label> </div> <div class="form-check"> <input type="radio" class="form-check-input" id="rotate90" name="rotate" value="90" autocomplete="off"> <label class="form-check-label" for="rotate90">90°</label> </div> <div class="form-check"> <input type="radio" class="form-check-input" id="rotateNeg90" name="rotate" value="-90" autocomplete="off"> <label class="form-check-label" for="rotateNeg90">-90°</label> </div> </div> </fieldset> <!-- Input fields for width and height --> <div class="image-form"> <input class="m-2" type="text" placeholder="Width" id="imageWidth"> <input class="m-2" type="text" placeholder="Height" id="imageHeight"> </div> <!-- Transform button --> <button class="btn btn-primary mt-2" (click)="transformImage()">Transform Image</button> </main>
Testing Our Image Editor App
Now that our app is complete, all we need to do is run our code using the following command:
ng serve
Here is what our app looks like:
We will start by loading up an image with a public id, turtles, from our Cloudinary assets. To do this, search for turtles in the search bar and click on the Load Image button:
Next, we can add different transformations to our image. For example, here, we have added a Cartoonify effect and rotated the image by 90 degrees:
Final Thoughts
Conclusively, an image editor is more than just a tool; it’s a creative companion for developers. With its rich feature set and seamless integration with Cloudinary, you can elevate your web applications by providing users with powerful image editing capabilities. Whether you’re building a photo-sharing app, an e-commerce storefront, or a portfolio website, an image editor app in a language like Angular is your go-to solution.
So, why wait? Sign up for Cloudinary and unlock new possibilities for your projects!
More from Cloudinary: