Webpack with angular 1.x and ES5 - angularjs

After reading hundreds of lines about browserify vs webpack and several how to of both I decided to go for webpack. The main reason it's because I liked the idea of bundling everything into a js file.
I have an angular project already working and I want to refactor it for webpack. The problem? My project is using angular 1.4.7, ng-animate and plain javascript (ES5) and all the tutorials and manuals are for ES6. I don't want to refactor my project so much. What's the way to go? I would like an example of each angular module : factory, directive, controller and so on. Many thanks

I typically have a feature.module.js file which has my module definition and requires all of the directives / services contained within the module. Also has the external dependancies.
/* module.js */
angular.module('my.module',['dependancy1', 'dependancy2']);
//External libraries
require('./dependancy1.module.js');
require('./dependancy2.module.js');
//Internal components
require('./thing.directive');
require('./thing.service';
/* service.js */
angular.module('my.module')
.directive('yourDir', function myDir(){...});

I'm dealing with the same problem now. And I found something that works (work in progress, but at least I can see progress). My steps:
Install yeoman
Run this angular-webpack generator. Select 'ES5' when asked (the other option is 'ES2015', which I guess is the same that 'ES6')
Start modifying the automatically generated boilerplate with your Angular code
Yes, you still need to learn about gulp and sass, but at least you can run a simple AngularJS app using the old ES5 syntax, and start modifying it.
I'm probably blogging about this. So, I'll update this answer then.

I tend to do this:
app.js:
require('/any/angular/deps');
var subModule = require('/some/sub/module');
var app = angular.module('myApp', []);
// pass the app module in sub modules to allow them to define their own config
subModule.configure(app);
/subModule/module.js:
var someSubDirective = require('./subDir/directive');
export function configure(app) {
someSubDirective.configure(app);
}
/subModule/subDir/directive.js:
export function configure(app) {
app.directive('myDir', myDir);
}
function myDir() {
}
My idea is to let all sub modules handle their own configuration, so declaring config or constant, factories or providers. Letting this then bubble up to the app.js. This means its really easy to delete a folder from your structure, because it is one line removal from it's parent module.
This also makes relevant file paths a lot shorter and easier to handle.

Related

Hybrid angular app throwing error "'./app.module.ajs.js' does not provide an export named 'default'"

I am attempting to convert my AngularJS project to run as a hybrid (i.e. 1.x alongside 2+) following the bootstrapping steps provided at the angular.io guide page provided specifically for the task. I have followed the guide with some modifications (primarily just so that I could maintain my existing project structure). My steps in inexact order have been to
include webpack
convert my original app.module.js and all angular modules declared therein to exportable ES modules
(app.module.js became app.module.ajs.ts, which is transpiled to .js)
add a new app.module.ts for manual bootstrapping, as per the docs (looks identical to the file of the same name in the docs) and remove the ng-app directive from the html as it is no longer necessary
import the modules into all dependent controller, direcive and service files.
Now when I serve the project, which I believe is the step I should be on (the steps seemed pretty straightforward...) I get a slew of the same error:
However when I open that file up, I do actually see exports.default = ariaApp.
Also, the .ts file by the same name does indeed contain the declaration and the default export
const ariaApp = angular.module("ariaApp", [])
export default ariaApp;
Has anyone seen this before? / Does anyone know what this error indicates? It seems totally off... All suggestions appreciated! Thank you!!

How to independence inject Browserify Crypto in Angular Ionic

I new bee of Ionic and angular. I used Browserify to require Crypto module of nodejs for my Ionic project. Following are my steps:
I added new line to app.js file
angular.module(.....).constant('Crypto',require('crypto'))
I run the command line, which create new file perfectly with code of Crypto module and app.js file
browserify app.js > bundle.js
I replace app.js by bundle.js in index.html file
I try to test it in service.js
angular.module('mapp.services').factory('abc',['$http','Crypto',function($http,Crypto){
return {
getAllProduct:function(){ console.log(Crypto.SHA256("Message"));
});
} }]);
It return message "Crypto.SHA256 is not a function". I think that "constant" means a value, not a static object, so that I cannot pass Crypto to "constant". So, how can I require all needed modules at app.js file by Browserify and then pass use it at other js file, like $http?
Thanks!
Sorry because of my stupid question. The fact that we can assign Crypto object to constant pool. The problem is my code. To encrypt a string, the code must be
crypto.createHash('sha256').update(mystr).digest('base64');
instead of
crypto.sha256(mystr)
Now problem solved, but thanks for your all reply and comments.
I don't think Crypto will work on the client, it's the server-side library for performing encryption and is largely a wrapper around OpenSSL (see here).
The functions that it wraps cannot be called in Ionic/Angular for the simple reason that they aren't available on the client.
Try using CryptoJS instead as was proposed by this answer - it was designed to be used client-side like in your Ionic/Angular project to perform cryto-related operations.

What's the use of exports / module.exports?

Today I've adopting Browserify for my AngularJS project, but there is something that's very unclear to me. In all examples and blog posts I've seen stuff like this:
/app.js:
require('./messages');
angular.module('sling', ['sling.messages']);
/messages/index.js:
exports = angular.module('sling.messages', [])
.controller('MessagesListCtrl', require('./MessagesListCtrl'));
/messages/MessagesListCtrl.js:
module.exports = function() {
// ...
});
Sure, this works, but why do this? I've implemented it like this and that works absolutely fine too and feels more normal for an AngularJS project:
/app.js:
require('./messages');
angular.module('sling', ['sling.messages']);
/messages/index.js:
angular.module('sling.messages', []);
require('./MessagesListCtrl');
/messages/MessagesListCtrl.js:
angular.module('sling.messages').controller('MessagesListCtrl', function() {
// ...
});
In other words, I'm completely skipping the exports/module.exports, only using require to basically include the files with the controllers, services, filter, etc.
Am I doing this right? I mean it all works, but am I going to be in trouble later on?
The main (and honestly only) reason to use Browserify is if you want to have CommonJS modules (i.e. NodeJS modules) in the browser. A CommonJS module stays out of the global scope by having an implicit 'module' scope. You choose what to expose from the module scope (usually the entry point or primary function of your module) into by augmenting the 'exports' object that every module has.
So a 'real' CommonJS module looks like this.
File A:
// a.js
function doSomething() {
console.log("I am doing something");
}
module.exports = doSomething
File B:
// b.js
doSomething();
// Exception - attempt to call a non-existent function
File C:
// c.js
var doSomething = require('a');
doSomething();
// logs "I am doing something"
In the browser which has no module scope, a.js would be augmenting the global scope with the doSomething function, since it's declared as a global. Browserify works around this by wrapping each bundled module into a function wrapper, and supplying an 'exports' object to the included modules as an argument to this wrapper.
Enter AngularJS. You've got two approaches you can use here, of which I'm assuming from the fact that you're not using require('angular') is the first:
Include AngularJS as a hard-coded script available in your index.html before your Browserify-transpiled bundle script.
Shim AngularJS so that it is attached to an exports object rather than the window (using something like browserify-shim), then importing it using require like any other module.
I tend to prefer the second approach because it's kind of weird to use Browserify to give you module scope and then make the major dependency of your project a window global.
However, AngularJS already has its own dependency-injection driven module system. When you declare angularJS components, you're attaching them to module objects which themselves have been attached to the angular object. This means that the exports object for your angularJS module files is essentially redundant in the case of Angular, since as long as the file executes then the angular object will be augmented with your module and components.
You still need to 'require' the files since otherwise Browserify won't bundle them, they'll never execute, and they'll never augment the angular object with your modules. But you don't need to add anything to the exports for Angular, since the angular object is your exports.
So why did I spend all this time explaining how CommonJS modules and exports work? Because hopefully the other reason why you are using Browserify is to allow you to make use of modules hosted on NPM in your browser application. Most of those modules are non-angular commonJS modules, meaning their functionality is exposed via an exports. In this case it's important to capture their exports in a variable when you require them, such as I'm doing in c.js above. Similarly, if you write some modules and release them to NPM, your users are going to expect you'll be adding the entry point to your module to the exports object of the file declared as main in your package.json.

Yeoman generator for angularjs

I'm starting learning angularJS and find out yeoman is quite useful. But some how the controller/service/model generated by yeoman is not good for minifying later. Because due to what I see through the generated template (service in this case ) we have to inject the service implicitly.
But if we want to minify later, it's recommended to inject explicitly using $inject.
So my question is : Is it correct that what I understand ? If it's not then what is the correct way to inject with generated template from yeoman.
Otherwise, we shouldn't use generated template from yeoman at the moment if we want to do minify later, right ?
Thanks
so yeoman gives you something like this when generating a service
testApp.factory('Thing', function(dep1, dep2) {
return {/*...*/};
});
This is problematic when the code is minified, because the process of minification shortens function parameters, and angular uses them to infer which dependencies to inject.
To inject dependencies I recommend the inline annotation which looks like this
testApp.factory('Thing', ['dep1', 'dep2', function(dep1, dep2) {
return {/*...*/};
}]);
Notice the second argument is an array that lists the proper dependency names and that its final item is a function where such dependencies will be injected.
Edit: The Angular generators for Yeoman now support minification for both JavaScript and CoffeeScript code with the --minsafe flag as shown by #Ramiro
You can use the Yeoman Angular generators with the --minsafe, for example:
yo angular:controller user --minsafe
check other options here:
https://npmjs.org/package/generator-angular
Edit:
A follow up on this. It's now unnecessary to use the --minsafe flag, as yeoman comes with the ngmin app, which automatically converts all applicable code to be minifiable, and then minifies it :)

Loading mocks into an AngularJS unit test

I'm trying to setup my AngularJS application to test out controllers, routes, templates and so on, but I'm having an issue getting some of the helper methods provided by the angular-mocks.js to work (namely module and inject).
I'm using testacular to load up the test suite with the following files added before the specs:
files = [
MOCHA,
MOCHA_ADAPTER,
'../application/lib/angular.min.js',
'./lib/angular/angular-mocks.js',
'./lib/angular/angular-scenario.js',
'../application/application.js',
'./lib/chai.js',
'./lib/chai-should.js',
'./lib/chai-expect.js',
'./spec/**/*.js'
];
So far so good, but when I run the tests I get this issue:
ReferenceError: Can't find variable: module
Not sure where this is loaded. Am I missing something?
First thing to check is that all those files are getting loaded in the test browser. It's surprisingly easy to get a path wrong in your config and not realize it. If you're running testacular with autowatch, you can navigate to http://localhost:9876/context.html with a browser and use developer tools inspect elements/resources/network and see if anything is missing.
If everything is good there and you're still having problems, post some of your test code and I'll take a look.
UPDATE: It appears (strangely) from the comments in the source for angular-mocks.js (line 1635) that window.module is only available with Jasmine. It looks like you're using Mocha instead of Jasmine. This is very likely the culprit.
ANSWER:
I can't rightly take credit for this Matsko, since you figured it out yourself... but it turns out that the current AngularJS stable download and angular-seed contain an older version of ngMock that doesn't support Mocha. Manually replacing the mock file with the latest from the github repo solves the problem. Glad I could help ;-)
I ran into this issue today and I wanted to provide others with a complete summary of the required steps to get this working. First let's say you have a module named myApp. Inside that that module there is a service called myModel. the myModel service has a method named getItems().
Currently, the angular-mocks.js (I am using AngularJS 1.0.6) does not support Mocha. You will need to visit this link and replace the 1.0.6 version with the one in the master branch from the AngularJS GitHub project. An easy way to do this (if you have wget) is wget https://raw.github.com/angular/angular.js/master/src/ngMock/angular-mocks.js in the correct directory. If you use a properly configured Sublime or vim it can also easily pull it down for you.
Make sure your karma.conf.js file includes the angular-mocks.js file in files array
Somewhere in your tests.js file (maybe at the top level describe) include beforeEach(module('myApp')); or whatever you named your main module.
If you plan to use the service (or whatever you want to include in the test) in more than one place you can call another beforeEach where needed like this:
beforeEach(inject(function(myModel) {
mymodel = myModel;
}));
otherwise you just can inject where it is needed. Now the mymodel variable (notice this is the variable you assigned in the beforeEach above) will be available to you for testing in your next blocks. For example, you can now write:
describe('when fetched', function() {
it('should return 3 items', function() {
// console.log(mymodel.getItems());
expect(mymodel.getItems()).to.have.length(3);
});
});

Resources