This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Angular with Webpack - Requiring Templates

2:48 Angular 1.x lesson by

With Angular, most of the time you're specifying a templateUrl for your directives and states/routes. This means you need to make sure that you're loading in these templates into the $templateCache for your tests and production. Oh, and don't forget to update all your URLs whenever you move the files around! When you add in Webpack and the raw-loader, you don't need to do this anymore. Simply require the html file and your work is done!

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

With Angular, most of the time you're specifying a templateUrl for your directives and states/routes. This means you need to make sure that you're loading in these templates into the $templateCache for your tests and production. Oh, and don't forget to update all your URLs whenever you move the files around! When you add in Webpack and the raw-loader, you don't need to do this anymore. Simply require the html file and your work is done!

Avatar
Andrew

How would this work with the new router and "component model" for angular 1.X?

The router does do a GET on an url, and the only capacity to change that, so far as I know, is to modify the URL by providing a function mapping component names to URLs. Presently, I am loading up files in $templateCache with the karma nghtml2js plugin and in production with one or another html packer.

In reply to egghead.io
Avatar
Kent C.

With the new router, I'm sure that your components will be able to have an inline template. I'll ask Brian about it though and get back to you!

In reply to Andrew
Avatar
Ben

Try the following interceptor to get around the components issue of not specifying an inline template:

angular.module('app').factory('TemplateInterceptor', ['$templateCache', function($templateCache) {
    return {
        request: function (config) {
            if(config.url.endsWith('.html')){
                $templateCache.put(config.url, require(config.url));
            }
            return config;
        }
    };
}]);
In reply to Kent C.
Avatar
Kent C.

That wont work with webpack because all the bundling happens before runtime, so webpack can't determine what to package up with that code (because it's a runtime require). Webpack can sometimes determine dynamic requires, but only if all the information is available as part of its static analysis.

With webpack, you never really have a need to use the $templateCache anyway, unless you want to preload it elsewhere. Then that could be useful. But it'll have to be hard-coded, not dynamic like this.

In reply to Ben
Avatar
John

how does it handle ng-include in the view?

Avatar
Kent C.

You can get ng-include to work with webpack, you'd just need to pre-load the $templateCache with all the templates you want by hand which shouldn't be a big deal.

However, I would highly recommend against using ng-include and instead use an element directive with isolate scope. It is impossible to know the API to a template using ng-include but with an isolate scope directive, you define your API so from a maintainability/reasonability standpoint, you're much better off.

In reply to John
Avatar
John

got it, thanks Kent

In reply to Kent C.
Avatar
Sander

It seems that in order to have webpack require a referenced image from a directive template (e.g. <img src="../../logo.png" />), html-loader is needed instead of raw-loader.

Avatar
Kent C.

Very good point! Thank you for correcting me there. I don't actually use img tags in my app at all so it works fine for me to use the raw-loader. But most people should probably be using the html-loader.

In reply to Sander
Avatar
Elia

I've tried to use raw loader for my component's templates, but (sometimes) i get this error:

In Chrome dev tools:
http://localhost:8080/%3Cdiv%20class='page-header'%3E%20%20%3Ch1%3E%7B%7B%2…%20%20%20%20%20%3C/div%3E%20%20%20%20%3C/div%3E%20%20%3C/form%3E%3C/div%3E 414 (Request-URI Too Large)

In console:
ENAMETOOLONG

How i can fix it?
Thanks

Avatar
Kent C.

That's probably happening because you're still using templateUrl rather than template

In reply to Elia
Avatar
Elia

You right! I've found that i use it in my router.
I've changed my components template, but not the router.
Now works, but i've an error: "jQuery is not defined".
Strange because i don't use it, maybe it's some package.
Webpack can't include it automatically if it's a dependency?

In reply to Kent C.
Avatar
Elia

I've figured it out installing jquery with npm and adding this loader:

test: /bootstrap\/js\//,
loader: 'imports?jQuery=jquery'

In reply to Elia
Avatar
J.C. Francis III

What do the package.json scripts look like for Windows? I can't get the build script working.

Avatar
Kent C.

Yeah, sorry. Try installing and using cross-env for the build script:

npm install -D cross-env

Then update build to:

"build": "cross-env NODE_ENV=production webpack && cp app/index.html dist/index.html"

Note, I edited the script a bit because you don't need node or to point directly to node_modules/.bin/webpack.

Good luck!

In reply to J.C. Francis III
Avatar
J.C. Francis III

Thanks this worked. I used xcopy [source] [destination] /Y to cp. Thanks again.

In reply to Kent C.
Avatar
Tim

Followed steps in this video but I'm getting a termial error when I ran webpack. Any ideas?

ERROR in ./scripts/Directive/socialMediabuttons.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./components/social-media-buttons.html in /app/scripts/Directive
resolve file

Avatar
Kent C.

That error would seem to indicate that webpack is unable to find a file in /app/scripts/Directive/components/social-media-buttons.html. If the file is actually there, my guess is that you may be setting your context incorrectly with the leading / in /app (it needs to be a full path, but it you probably don't mean the app directory in your root directory).

Good luck!

In reply to Tim
Avatar
Joseph

I'm getting the following error:

in ./app/directives/kcd-hello.html
Module parse failed: /Users/joe/Dev/angular-webpack/app/directives/kcd-hello.html Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (1:0)

In my webpack config:

modules: {
        loaders: [
            {
                test: /\.js$/,
                loader: 'babel',
                exclude: '/node_modules'
            },
            {
                test: /\.html$/,
                loader: "raw",
                exclude: '/node_modules'
            }
        ]
    }

And in my directive:

module.exports = function (ngModule) {
    ngModule.directive("kcdHello", function () {
        return {
            restrict: 'E',
            scope: {},
            template: require('./kcd-hello.html'),
            controllerAs: 'vm',
            controller: function (){
                var vm = this;
                vm.greeting = 'hello webpack'
            }
        }
    })
}

Any ideas why i'm still getting the error? My template is just a simple div with {{vm.greeting)). I also tried html-loader, but that failed too.

Avatar
Joseph

So after tearing my hair out for a while, i think this was the issue.
Webpack config should have been:

module: {
        loaders: [ ...

not:

modules: {
        loaders: [....
In reply to Joseph
Avatar
Kent C.

Sorry to hear that you had that trouble! I recommend that you give webpack-validator a look. You would have caught that spelling error with webpack-validator :-)

In reply to Joseph

When you're using Webpack, you have a really huge benefit that comes with all of your resolve statements being executed before your files are loaded into the browser. One thing that is insanely powerful is instead of using templateUrl, you can use template everywhere.

You don't need to worry about the templateCache or XHRs firing off to go get your templates. The way that this works is you simply require the file there. You're done. You do need to add a loader, but what's really nice about this is this makes things a lot more modular.

We could move these two files anywhere together. This component would still work. We wouldn't have to change the templateUrl to be pointing to the correct place wherever you move the file. This is a huge win that Webpack gives us.

If we were to save this and refresh, we're going to get an error right here. You need an appropriate loader to handle this file type. Webpack doesn't understand what on Earth this is talking about.

To accomplish this, we need a new loader. We'll npm install the raw loader. We'll add that as a dev dependency. We'll go to our Webpack config. We'll add a new loader here. Anything that ends in an HTML, we want it to use raw or the raw loader. Pro tip. This and this are the same thing. If you don't want it to be so verbose, then you just leave off the "loader."

Now, if we restart our dev server, everything should work properly. We refresh. Everything is good to go. You no longer need to use templateUrl or template anything. In fact, with something so simple as this, we don't even need to reference that. Because this application is using ES6, we could just as easily put our template right there. No worse for wear.

It depends on your style. Obviously, you're not going to want to do this if your template is very large, but as it is, it works really well just to use the template string with the backticks so we have multilines. That is loading your HTML, getting rid of your templateUrl, and just using template.

This really resolves a lot of the concerns with the HTTP backend when you're testing to make sure that you're loading stuff with ng-to-JS HTML loader plugins for Karma or whatever. You don't need to worry about that anymore with Webpack. That's another awesome thing that Webpack brings to the table with Angular.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?