Keeping images with your components, or: How to avoid weird paths and be more modular

The Problem

Isn’t module design great, especially on the front-end. I mean it works everywhere, but those front end modules are so easy to reuse wherever you need them. Even if you aren’t publishing to npm, just keeping things so self contained makes refactoring easier, reduces git conflicts, makes testing easier…ya you already know this probably. Beautiful, let’s look at some css from a header component in a recent Angular project. This css file lives right with the component.ts file and html file, all in their little self contained folder. Beautiful.

 

  .logo {
  background: url(../../../assets/images/header-bg.png);
  background-size: cover;
}

Hmm…how much do you love that url to the background? Ugh. We’ve made such great strides to keep our design nice and modular with all our other code all together, yet images still all live separately in one giant images folder. Also it’s really ugly and annoying to deal with. I don’t know about you, but I’m not really a fan of counting folder nesting levels while typing ../ over and over again. Everything else this component needs to work is right here, self-contained. Why can’t images that are part of the component also be self contained? They can!

How? Webpack. While my example will use Angular, this is all done with Webpack and will work just fine with any framework. I’ll show you how your code will work first, then the configuration necessary to make it work.

The Solution

First, move the image into the same folder as your component. Then, use “require” to set the image to a public property of your component, here mine is simply named “background”.

  @Component({
  selector: 'app-header',
  templateUrl: './header.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent {
  background = require('./header-bg.png');

  constructor(private authService: AuthenticationService) {}

  logout(): void {
    this.authService.logout();
  }
}

You can see “background” is now a public property on my HeaderComponent. You can now use this in your html just like any other public property. You can set it to [src] in an image, or use ngStyle to set a background.

<img [src]="background">
<!-- Or, set it as a background style  -->
<div class="header" [ngStyle]="{'background-image': 'url(' + background + ')'}"></div>

That’s actually it, it really is that simple, if Webpack is configured properly.

The Configuration

If you’re using the Angular CLI, you already have the configuration you need. The only catch is that TypeScript is going to complain about not knowing what “require” is. You can fix this very easily by installing @types/node with

npm install @types/node --save

And then adding “node” to the types array in your tsconfig.app.json file (under src/).

If you’re using vanilla Webpack, you want to configure url-loader:

{
  test: /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
  loader: "url-loader",
  options: {
    name: "[name].[hash:20].[ext]",
    limit: 10000,
    outputPath: 'assets/'
  }
},

This is pretty standard Webpack config, any files that pass the test are loaded with url-loader with the options defined. A nice thing about url loader (as opposed to file-loader) is that it can return a DataURL if the file is small enough. The limit option sets that limit, outputPath says where to output the required images when building, and name lets you set name options: here a hash is set. This will make sure that you won’t have naming conflicts if two components contain two different files with the same name.

And that’s it. You can now bundle your images with your components and modules. Your code is now movable, reusable, and fully self contained.

Wrap up

When I was first learning Angular 2 I struggled with this question, and asked about it on StackOverflow. A year later a user named David Blaney suggested using Webpack for this, so thanks to him. The solution was great, and I wanted to expand on it a little bit here to explain the need for a proper Webpack configuration with url-loader. Keep in mind this is not a solution you should universally apply. This is great when the image is actually a part of the component, but if the image is generic and might be used multiple times outside of the component it’s probably better to keep it in the images folder. Please let me know what you think in the comments below.