Let's Build An Image Cropper With Angular

Publish date

In this short guide we'll build an image cropper component with Angular and Pintura. The component will allow us to crop photos in various aspect ratios resulting in better quality user generated content.

We'll start from scratch with a blank project, if you already have a project you want to add an image cropper component, then you can skip to Installing Pintura Image Editor

Let's get started!

Creating A Project

We'll type the following in our terminal to create a new Angular project.

ng new cropper-tutorial

We'll press return a couple times to choose default answers to the tech stack questions. This will start the installation process.

⠹ Installing packages (npm)...

That'll run for a couple seconds until it shows.

✔ Packages installed successfully.

We can now install Pintura.

Installing Pintura Image Editor

Let's install the Pintura component, this will make it super easy to build our image cropper.

npm i @pqina/angular-pintura @pqina/pintura

When this is done we make a small change to the angular.json file.

// 1. Navigate to: projects > cropper-tutorial > architect > build > options
styles: [
    'src/styles.css',

    // 2. Add the Pintura style sheet
    'node_modules/@pqina/pintura/pintura.css',
]

We save the file and then we can start the development server by typing:

npm start

It'll show the following message if all is fine.

✔ Compiled successfully.

Let's open the project in our browser at http://localhost:4200/, it'll show the default Angular project page.

Loading Pintura Image Editor

We'll start by editing the app.module.ts file so we can add the Pintura module.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

// 1. We import the AngularPinturaModule
import { AngularPinturaModule } from '@pqina/angular-pintura';

@NgModule({
    declarations: [AppComponent],
    imports: [
        BrowserModule,

        // 2. We add the AngularPinturaModule to the imports list
        AngularPinturaModule,
    ],
    providers: [],
    bootstrap: [AppComponent],
})
export class AppModule {}

Now we'll open the app.component.ts file so we can add our default editor options.

import { Component } from '@angular/core';

// 1. Import the editor default configuration
import { getEditorDefaults } from '@pqina/pintura';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
})
export class AppComponent {
    title = 'cropper-tutorial';

    // 2. Our default editor configuration
    defaultOptions: any = {
        // The editor defaults, this includes locale and image loaders
        ...getEditorDefaults(),

        // Our custom options (enable dropping and browsing for images on editor click)
        enableDropImage: true,
        enableBrowseImage: true,
    };
}

Next we'll open app.component.html and we'll replace its contents with:

<div style="height:800px">
    <pintura-editor [options]="defaultOptions"></pintura-editor>
</div>

After saving the page your browser will refresh and show "Waiting for image"

If you click the editor you can select an image and start editing.

Configuring Our Cropper

We've now loaded a full blown image editor, we'll probably not need all this functionality so let's slim it down a bit.

We can alter the defaultOptions object to customize editor functionality

export class AppComponent {
    defaultOptions: any = {
        // The editor factory settings
        ...getEditorDefaults(),

        // Our preferences
        enableDropImage: true,
        enableBrowseImage: true,

        // Let's only load the crop util
        utils: ['crop'],

        // We only want square crops
        imageCropAspectRatio: 1,
    };
}

Receiving The Resulting Cropped Image

Currently nothing happens when we click the Export button. Let's adjust our code so we can see the resulting image.

First we add an <img> tag to the app.component.html file, then we'll add the "process" event handler.

<div>
    <!-- 1. Add this image tag, it'll hold the result -->
    <img *ngIf="result" [src]="result" alt="" style="max-width:100%" />

    <div style="height:800px">
        <!-- 2. Add the `handleProcess` event handler -->
        <pintura-editor
            [options]="defaultOptions"
            (process)="handleProcess($event)"
        ></pintura-editor>
    </div>
</div>

Now we update the app.component.ts so we can handle the resulting image.

import { Component } from '@angular/core';
import { getEditorDefaults } from '@pqina/pintura';

// 1. Import the DomSanitizer so we can use the image Object URL
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
    // 2. Need to use the DomSanitizer
    constructor(private sanitizer: DomSanitizer) {}

    // 3. This will hold the resulting image
    result?: string = undefined;

    // 4. This will handle the process event
    handleProcess($event: any) {
        // `dest` will hold a file object, but we need a URL
        const objectURL = URL.createObjectURL($event.dest);

        // We'll sanitize the URL and use it as a string
        this.result = this.sanitizer.bypassSecurityTrustResourceUrl(
            objectURL
        ) as string;
    }

    // Our default options, nothing changed here
    defaultOptions: any = {
        ...getEditorDefaults(),
        enableDropImage: true,
        enableBrowseImage: true,
        utils: ['crop']
    };
}

Now when we edit the image the resulting image will be shown above the editor.

Cropping A Square Image

Let's adjust our code so we can crop a square image. We want to tell the editor that when an image loads we only allow a square crop, so let's do that now.

<div>
    <img *ngIf="result" [src]="result" alt="" style="max-width:100%" />

    <div style="height:800px">
        <!-- 1. Add the #myEditor ref -->
        <!-- 2. Add the `handleLoad` event handler -->
        <pintura-editor
            #myEditor
            [options]="defaultOptions"
            (load)="handleLoad($event)"
            (process)="handleProcess($event)"
        ></pintura-editor>
    </div>
</div>

We now have to handle the load event in the app.component.ts file.

// 1. Import ViewChild so we can reference the editor
import { Component, ViewChild } from '@angular/core';
import { getEditorDefaults } from '@pqina/pintura';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
    // 2. Add ViewChild decorator so we can reference the editor
    @ViewChild('myEditor') myEditor?: any = undefined;

    constructor(private sanitizer: DomSanitizer) {}

    result?: string = undefined;

    // 3. We handle the load event, when an image is loaded the crop aspect ratio is set to 1.
    handleLoad($event: any) {
        this.myEditor?.editor?.imageCropAspectRatio = 1;
    }

    handleProcess($event: any) {
        const objectURL = URL.createObjectURL($event.dest);
        this.result = this.sanitizer.bypassSecurityTrustResourceUrl(
            objectURL
        ) as string;
    }

    // Our default options, nothing changed here
    defaultOptions: any = {
        ...getEditorDefaults(),
        enableDropImage: true,
        enableBrowseImage: true,
        utils: ['crop']
    };
}

That's it! I hope you found this an interesting read, feel free to reach out with any questions.

You can find a Pintura Angular example project on GitHub it includes a link to a live code environment where you can run the project online.