Skip to main content

3 posts tagged with "angular"

View All Tags

The Mysterious Case of Webpack, Angular and jQuery

You may know that Angular ships with a cutdown version of jQuery called jQLite. It's still possible to use the full-fat jQuery; to quote the docs:

To use jQuery, simply ensure it is loaded before the angular.js file.

Now the wording rather implies that you're not using any module loader / bundler. Rather that all files are being loaded via script tags and relies on the global variables that result from that. True enough, if you take a look at the Angular source you can see how this works:

// bind to jQuery if present;
var jqName = jq();
jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
!jqName ? undefined : // use jqLite
window[jqName]; // use jQuery specified by `ngJq`

Amongst other things it looks for a jQuery variable which has been placed onto the window object. If it is found then jQuery is used; if it is not then it's jqLite all the way.

But wait! I'm using webpack#

Me too! And one of the reasons is that we get to move away from reliance upon the global scope and towards proper modularisation. So how do we get Angular to use jQuery given the code we've seen above? Well, your first thought might be to npm install yourself some jQuery and then make sure you've got something like this in your entry file:

import "jquery"; // This'll fix it... Right?
import * as angular from "angular";

Wrong.

You need the ProvidePlugin#

In your webpack.config.js you need to add the following entry to your plugins:

new webpack.ProvidePlugin({
"window.jQuery": "jquery"
}),

This uses the webpack <a href="https://github.com/webpack/docs/wiki/list-of-plugins#provideplugin">ProvidePlugin</a> and, at the point of webpackification (© 2016 John Reilly) all references in the code to window.jQuery will be replaced with a reference to the webpack module that contains jQuery. So when you look at the bundled file you'll see that the code that checks the window object for jQuery has become this:

jQuery = isUndefined(jqName) ? __webpack_provided_window_dot_jQuery : // use jQuery (if present)
!jqName ? undefined : // use jqLite
window[jqName]; // use jQuery specified by `ngJq`

That's right; webpack is providing Angular with jQuery whilst still not placing a jQuery variable onto the window. Neat huh?

Inlining Angular Templates with WebPack and TypeScript

This technique actually applies to pretty much any web stack where you have to supply templates; it just so happens that I'm using Angular 1.x in this case. Also I have an extra technique which is useful to handle the ng-include scenario.

Preamble#

For some time I've been using webpack to bundle my front end. I write ES6 TypeScript; import statements and all. This is all sewn together using the glorious ts-loader to compile and emit ES6 code which is handed off to the wonderful babel-loader which transpiles it to ESold code. All with full source map support. It's wonderful.

However, up until now I've been leaving Angular to perform the relevant http requests at runtime when it needs to pull in templates. That works absolutely fine but my preference is to preload those templates. In fact I've written before about using the gulp angular template cache to achieve just that aim.

So I was wondering; in this modular world what would be the equivalent approach? Sure I could still use the gulp angular template cache approach but I would like something a little more deliberate and a little less magic. Also, I've discovered (to my cost) that when using the existing approach, it's possible to break the existing implementation without realising it; only finding out there's a problem in Production when unexpected http requests start happening. Finding these problems out at compile time rather than runtime is always to be strived for. So how?

raw-loader!#

raw-loader allows you load file content using require statements. This works well with the use case of inlining html. So I drop it into my webpack.config.js like so:

var path = require('path');
module.exports = {
cache: true,
entry: {
main: './src/main.ts',
vendor: [
'babel-polyfill',
'angular',
'angular-animate',
'angular-sanitize',
'angular-ui-bootstrap',
'angular-ui-router'
]
},
output: {
path: path.resolve(__dirname, './dist/scripts'),
filename: '[name].js',
chunkFilename: '[chunkhash].js'
},
module: {
loaders: [{
test: /\.ts(x?)$/,
exclude: /node_modules/,
loader: 'babel-loader?presets[]=es2015!ts-loader'
}, {
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015']
}
}, { // THIS IS THE MAGIC!
test: /\.html$/,
exclude: /node_modules/,
loader: 'raw'
}] // THAT WAS THE MAGIC!
},
plugins: [
// ....
],
resolve: {
extensions: ['', '.ts', '.tsx', '.js']
}
};

With this in place, if someone requires a file with the html suffix then raw-loader comes in. So now we can swap this:

$stateProvider
.state('state1', {
url: "/state1",
templateUrl: "partials/state1.html"
})

For this:

$stateProvider
.state('state1', {
url: "/state1",
template: require("./partials/state1.html")
})

Now initially TypeScript is going to complain about your require statement. That's fair; outside of node-land it doesn't know what require is. No bother, you just need to drop in a one line simple definition file to sort this out; let me present webpack-require.d.ts:

declare var require: (filename: string) => any;

You've now inlined your template. And for bonus points, if you were to make a mistake in your path then webpack would shout at you at compile time; which is a good, good thing.

ng-include#

The one use case that this doesn't cover is where your templates import other templates through use of the ng-include directive. They will still trigger http requests as the templates are served. The simple way to prevent that is by priming the angular <a href="https://docs.angularjs.org/api/ng/service/$templateCache">$templateCache</a> like so:

app.run(["$templateCache",
($templateCache: ng.ITemplateCacheService) => {
$templateCache.put("justSome.html", require("./justSome.html"));
// Other templates go here...
}]);

Now when the app spins up it already has everything it needs pre-cached.

Creating Angular UI Routes in the Controller

So you're creating a link with the Angular UI Router. You're passing more than a few parameters and it's getting kinda big. Something like this:

<a class="contains-icon"
ui-sref="Entity.Edit({ entityId: (vm.selectedEntityId ? vm.selectedEntityId: null), initialData: vm.initialData })">
<i class="fa fa-pencil"></i>Edit
</a>

See? It's too long to fit on the screen without wrapping. It's clearly mad and bad.

Generally I try to keep the logic in a view to a minimum. It makes the view harder to read, it makes behaviour of the app harder to reason about. Also, it's not testable and (if you're using some kind of static typing like TypeScript) it is entirely out of the realms that a compiler can catch. So what to do? Move the URL generation to the controller. That's what I decided to do after I had a typo in my view which I didn't catch until post-commit.

ui-sref in the Controller#

Actually, that's not exactly what you want to do. If you look at the Angular UI Router docs you will see that ui-sref is:

...a directive that binds a link (&lt;a&gt; tag) to a state. If the state has an associated URL, the directive will automatically generate & update the href attribute via the $state.href() method.

So what we actually want to do is use the $state.href() method in our controller. To take our example above we'll create another method on our controller called getEditUrl

export class EntityController {
$state: angular.ui.IStateService;
static $inject = ["$state"];
constructor($state: angular.ui.IStateService) {
this.$state = $state;
}
//... Other stuff
getEditUrl() {
return this.$state.href("Entity.Edit", {
selectedEntityId: this.selectedEntityId ? this.selectedEntityId: null,
initialData: this.initialData
});
}
}

You can see I'm using TypeScript here; but feel free to strip out the type annotations and go with raw ES6 classes; that'll still give you testability if not static typing.

Now we've added the getEditUrl method we just need to reference it in our view:

<a class="contains-icon" ng-href="{{vm.getEditUrl()}}"><i class="fa fa-pencil"></i>Edit</a>

Note we've ditched usage of the ui-sref directive and gone with Angular's native <a href="https://docs.angularjs.org/api/ng/directive/ngHref">ng-href</a>. Within that directive we execute our getEditUrl as an expression which gives us our route. As a bonus, our view is much less cluttered and comprehensible as a result. How lovely.