Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Randy Brown
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Randy Brown

Pasting Images Into Your App Using File Blobs And URL.createObjectURL() In Angular 7.2.15

By Ben Nadel on

In the past couple of months, I've been playing around a lot more with File handling in Angular. Things like reading a drag-and-drop text File, uploading a single File with HttpClient and, uploading multiple File objects as a single Form Post all turn out to be somewhat simple in Angular. As another fun experiment in file handling, I waned to see if I could allow the user to Paste a copied Image File from their computer's clipboard right into my Angular 7.2.15 app.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

A few years ago, I learned about the ability to render image previews using "Object URLs". This approach uses the URL.createObjectURL() method to convert a Blob (binary object) into an addressable URL like:

blob:http://127.0.0.1:56809/92ece87b-9242-4cf5-b027-90bdb7939dbb

This URL can then be treated just like any other URL; and, in particular, can be used as the src attribute for an image (or the href attribute of an anchor tag). This allows us to navigate to dynamic data without having to worry about Base64-encoding complexities and URL length limitations.

Given this functionality - which has been supported since Internet Explorer 10 (IE10) - I wanted to see if I could capture the File object attached to a Window paste event, turn that File object into an "Object URL", and then render it in my Angular application.

To explore this idea, I created an App component that provides a few demo images (of Wildlings from Game of Thrones). These images can be right-clicked for "Copy Image" functionality. Then, if the user pastes (Cmd+V) anywhere in the Browser window, I'm going to intercept the event, extract the File, and then render it in a list of img elements:

// Import the core angular services.
import { Component } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { SafeUrl } from "@angular/platform-browser";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "my-app",
	host: {
		"(window:paste)": "handlePaste( $event )"
	},
	styleUrls: [ "./app.component.less" ],
	template:
	`
		<h2>
			Sample Images (That You Can Right-Click And Copy)
		</h2>

		<p class="sample-images">
			<img src="./img/image-1.jpg" class="sample-image" />
			<img src="./img/image-2.jpg" class="sample-image" />
			<img src="./img/image-3.png" class="sample-image" />
			<img src="./img/image-4.jpg" class="sample-image" />
			<img src="./img/image-5.jpg" class="sample-image" />
		</p>

		<h2>
			Pasted Images
		</h2>

		<p class="images">
			<ng-template ngFor let-imageUrl [ngForOf]="imageUrls">

				<img [src]="imageUrl" class="image" />

			</ng-template>
		</p>
	`
})
export class AppComponent {

	public imageUrls: SafeUrl[];

	private lastObjectUrl: string;
	private sanitizer: DomSanitizer;

	// I initialize the app component.
	constructor( sanitizer: DomSanitizer ) {

		this.sanitizer = sanitizer;

		this.imageUrls = [];
		this.lastObjectUrl = "";

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I handle the paste event on the Window (see host bindings).
	public handlePaste( event: ClipboardEvent ) : void {

		var pastedImage = this.getPastedImage( event );

		if ( ! pastedImage ) {

			return;

		}

		// When we create Object URLs, the browser will keep them in memory until the
		// document is unloaded or until the URL is explicitly released. Since we are
		// going to create a new URL every time the user pastes an image into the app (in
		// this particular demo), we need to be sure to release the previous Object URL
		// before we create the new one.
		// --
		// NOTE: One the Image is rendered in the DOM, releasing the Object URL will not
		// affect the rendering.
		if ( this.lastObjectUrl ) {

			URL.revokeObjectURL( this.lastObjectUrl );

		}

		// At this point, the "pastedImage" is a File object, which is a specialized type
		// of "Blob". We can now generate a "blob:" URL using the given File.
		this.lastObjectUrl = URL.createObjectURL( pastedImage );

		// By default, Angular WILL NOT TRUST this "blob:" style URLs. However, since we
		// know these are going to be expected, we can use the DOM Sanitizer to bypass
		// the security checks on these images.
		// --
		// NOTE: The sanitizer doesn't return Strings - it returns SafeUrls. 
		this.imageUrls.unshift(
			this.sanitizer.bypassSecurityTrustUrl( this.lastObjectUrl )
		);

	}

	// ---
	// PRIVATE METHODS.
	// ---

	// I return the first Image File from the given paste event (or null).
	private getPastedImage( event: ClipboardEvent ) : File | null {

		// NOTE: I am not very familiar with the Paste Event. As such, I am probably
		// being more cautious here than I need to be. However, in an abundance of
		// caution, I am checking each part of the targeted object path.
		if (
			event.clipboardData && 
			event.clipboardData.files && 
			event.clipboardData.files.length &&
			this.isImageFile( event.clipboardData.files[ 0 ] )
			) {

			return( event.clipboardData.files[ 0 ] );

		}

		return( null );

	}


	// I determine if the given File is an Image (according do its Mime-Type).
	private isImageFile( file: File ) : boolean {

		return( file.type.search( /^image\//i ) === 0 );

	}

}

As you can see, this turns out to be a fairly simple workflow. The ClipboardEvent contains a list of File objects. I grab the first File in the list, check to make sure its mime-type matches image/*, and then I generate an Object URL which I store in my view-model.

If I run this Angular app in the browser and copy-paste a few of the images, we get the following output:

Copy and pasting images into an Angular 7.2.15 app using the paste event and Object URLs.

The one hoop that we have to jump through is Security. By default, Angular tries to protect us from potentially malicious behavior. And, it categorizes blob: URLs as potentially malicious. So, in order for us to be able to consume the generated Object URL in our Image src attribute, we have to pass it through the DOMSanitizer, which will return an instance of SafeUrl (this is not a String).

The more I play with File management in my Angular applications, the more I am delighted to see that modern Browser APIs make things rather simple. I think this opens up a whole world of interesting interaction workflows, like being able to paste Images directly into an application. This is definitely something I'm going to start thinking about at InVision.



Reader Comments

Hi this is great idea i liked to and i have doubt here how to upload pasted (attached) images in to the serve.Can i upload blob URL Directly ?
Could you help me please.

Thank you

Reply to this Comment

@Thejesh,

I'm glad you find this stuff interesting. Yes, you can definitely upload a pasted-image. The paste event actually contains a File object. And, you should be able to take that File object and just upload it using an HTTP client. For example, in this post:

https://www.bennadel.com/blog/3593-uploading-files-with-httpclient-in-angular-7-2-11.htm

.... I am uploading a File object via the HttpClient in Angular. In that post, I was selecting the File using the File Input element; but, you could just as easily -- I am pretty sure -- use the paste event to do the same thing.

Let me know if that helps. Otherwise, I can try to put together a demo.

Reply to this Comment

@Thejesh,

I am not sure. I've been using Markdown as my editing approach (which is nice because it doesn't require much client-side functionality). But, I see there is a Ticket here about adding a rich text editor to Angular Material:

https://github.com/angular/material/issues/8487

... while there isn't one currently, it looks like there are several mentioned in the ticket comments.

Reply to this Comment

Hi,
I love this approach. Anyhow, when I tried copying an image with transparent background from windows application such as powerpoint, and pasting it in web app, the transparency is not preserved. Image is being displayed with white background.
Could you provide some idea on how to maintain the transparency?
Thanks in advance

Reply to this Comment

@Ganesh,

I am not sure what in this process would be responsible for stripping the transparency from the image. After all, we're not modifying the image - we're just pasting the binary data into the Angular app and then uploading it via AJAX.

As a sanity check, I would try right-clicking the image in the source page and do "Save As" to your local computer. And then, confirm that the saved image has transparency. It could be that there's something confusing about the image?? I'm not really sure.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.