AngularJS + RequireJS Module Definitions - javascript

I've got a fairly large project that uses both RequireJS and AngularJS. Everything works fine but looking at my code its kind of a mess with module definitions. For example here is the 'main' app file:
define(['angular',
'jquery',
'ui-router',
'angular-restmod',
'angular-cookies',
'angular-animate',
'angular-sanitize',
'angular-touch',
'angular-moment',
'angular-ui-select2',
'bower_components/jquery-ui/ui/resizable',
'bower_components/jquery-ui/ui/draggable',
'bower_components/jquery-ui/ui/sortable',
'bower_components/jquery-ui/ui/droppable',
'angularui',
'angular-hotkeys',
'splitter',
'angular-loading-bar',
'common/components/chart/chart',
'common/utils/confirmLeave',
'common/utils/listener',
'common/components/toolbar/toolbar',
'common/components/anchor/anchor',
'common/utils/print',
'common/components/notifications/notifications',
'angular-shims-placeholder'], function (angular, $) {
var app = angular.module('myapp', ['restmod', 'ngCookies', 'ui.router', 'oc.lazyLoad', 'ngAnimate', 'ngSanitize', 'angularMoment',
'ui.bootstrap', ui.select2', 'utils.listener', 'ng.shims.placeholder', 'interceptors.exceptions', 'interceptors.security', 'cfp.hotkeys', 'utils.offline', 'components.notifications', 'angular-loading-bar']);
return module;
});
if you notice I define both the the amd module and the angular module twice. Its pretty nasty looking ( even though it does work fine ).
My question is has anyone found a better approach for this? I've seen this ngDefine project but I have my reservations about it.

I ended up writing my own little utility to handle this...
define(['angular'], function(angular){
angular.defineModule = function dm(name, modules){
var pargs = dm.caller.arguments;
var filtered = Array.slice(pargs)
.filter(function (m) { return m && m.name; })
.map(function (mm) { return mm.name; });
modules && (filtered = filtered.concat(modules));
return angular.module(name, filtered);
};
});
then you can use it like:
define(['angular',
'core',
'common/services/users',
'common/services/account',
'common/components/toolbar/toolbar',
'common/components/multi-select/multi-select',
'./change-avatar',
'./change-password',
'less!app/admin/users/detail/detail'], function (angular) {
var module = angular.defineModule('admin.users.detail');
// fun code here //
return module;
});
the caveat is all your require modules have to return the angular module at the end. If they don't, then I created a 'shim' technique where you can pass it in manually like:
var module = angular.defineModule('admin.roles.settings', ['textAngular']);
Interested to get others feedback...

You're not loading the modules twice; you are loading the modules once so that it can be found when you declare your application-module's dependencies. As AMD module you depend on them the first time at which time they are physically loaded and as AngularJS module you depend on them but that's only possible when they are already visible.
What we do to cut away some of this boilerplate code is have a generic core.js AMD module which loads all the libraries we are going to need always anyway (jquery, plugins, angular etc.). Then our main app file would simply depend on the core module.
You can take this further, by defining an AngularJS 'core' module as well. Then all you need is:
define(['core'], function (angular, $) {
return angular.module('myapp', ['core']);
});
You moved it out, but also made it reusable over multiple modules for both RequireJS and AngularJS.
Regarding ngDefine, it seems like syntactical sugar. Considering mixing AngularJS with RequireJS already provides the necessary learning curve, alienating the syntax more while not gaining a significant advantage seems like a choice you can skip. Ask yourself: "Our current approach is ugly, but do I do this a lot? Is it a real problem worth another library and a new way of defining angular apps?". Probably not!

Related

Minifying Angular not updating templates?

Fairly new to developing in angular so please bear with me.
I happen to be developing components for AEM 6.2 using angular using gulp to minify the js for all the components by doing the following in the gulpfile:
var uglify = require('gulp-uglify');
var pump = require('pump');
var gp_concat = require('gulp-concat');
var gp_rename = require('gulp-rename');
var gp_ignore = require('gulp-ignore');
var gp_htmlmin = require('gulp-htmlmin');
var templates = require('gulp-angular-templatecache');
var paths = require('../paths');
var utils = require('../utils');
var base = [
paths.APP,
paths.ETC,
paths.DESIGN
];
gulp.task('minify', function () {
var filesToInclude = ['**/app/components/**/*.js '];
var excludeCondition = '**/*.spec*.js'
return gulp.src(filesToInclude)
.pipe(gp_ignore.exclude(excludeCondition))
.pipe(gp_concat('all.concat.js'))
.pipe(gulp.dest('dist'))
.pipe(gp_rename('all.min.js'))
.pipe(plugins.ngAnnotate())
.pipe(uglify())
.pipe(gulp.dest('dist'));
});
Now the minification of the js works perfect. However, in the templates (i.e. the html for each component), the references to the controller aren't using the minified name. For example, instead of the minified controller name, it's still using the original controller name:
<section data-ng-controller="MyController as mc" ng-cloak>
<div class="mc-name">
Hi, {{mc.userName}}
</div>
</section>
Again, I'm fairly new to angular so I'm not sure how the templates and controllers are linked in such a way that the minification knows to update all references. Could someone help shed some light on what I'm missing? Thanks!
I had a similar issue a little while back in AEM6.1 and from memory it was related to competing versions of minification IE: YUI Compressor. I'm stretching my memory, but I believe the 6.1 version of YUI Compressor was seriously outdated. I recall it being updated in 6.2, but I would still suspect it may be competing with your angular minification, probably ^latest. Essentially, you are trying to minify twice, again probably with different versions of YUI Compressor.
Now you could turn off minification in AEM, and simply run with the Angular minification (in theory), but I wouldn't suggest doing that as it would mean any AEM clientLibs are not minified.
Alternatively, don't minify with angular compiler, allow AEM to do the minification for you. Which I think is what I did at the time. Although I recall getting a hotfix through adobe daycare at some point for the same, so I can't be certain, it was a couple of years ago now.
Please try to use the controller with vm and injector syntax
`rmToolkit.controller('QuestionsCtrl', QuestionsCtrl);
QuestionsCtrl.$inject = ['$scope', '$rootScope', '$stateParams', '$state','Data_Service'];
function QuestionsCtrl($scope, $rootScope,) {
var vm = this;
}`
Hopes this syntax will resolve the minification issues

JavaScript Faux Pas - Let me put global libraries on the window?

I'm using RequireJS. I absolutely hate the double variable syntax of defining a dependency and passing it in as a variable in a callback function. I am therefore attempting the implement the 'Sugar' syntax available in RequireJS.
However, I only want to 'import' global libraries like Backbone, jQuery, Underscore and Marionette once. jQuery and Backbone obviously assign themselves to the Window object but Underscore and Marionette do not.
This is my main.js file:
require.config({
paths: {
"jquery" : "vendor/jquery.min",
"underscore": "vendor/underscore-min",
"backbone" : "vendor/backbone-min",
"marionette" : "vendor/marionette",
"app" : "app/app"
}
});
define(function(require, exports, module) {
// Make these libraries available globally
var jquery = require('jquery');
window.underscore = require('underscore');
var Backbone = require('backbone');
window.marionette = require('marionette');
// Require and start our own app
var app = require('app');
app.start();
});
This obviously stops me from having to import/require each of these core libraries into every subsequent module/component for my application. Taking my code from potentially this (app.js file):
define(function (require, exports, module) {
var jquery = require('jquery'),
underscore = require('underscore'),
Backbone = require('backbone'),
Marionette = require('marionette'),
// module specific libs
mymodule = require('../js/app/module'),
logger = require('../js/app/logger');
return {
start: function () {
var testview = new mymodule();
logger.logme();
}
}
});
To this (better app.js):
define(function (require, exports, module) {
var mymodule = require('../js/app/module'),
logger = require('../js/app/logger');
return {
start: function () {
var testview = new mymodule();
logger.logme();
}
}
});
Much cleaner IMO.
So thoughts? Criticisms? Are these going to play well together? (two already do it themselves - why not for the other two if they are core to the app).
In my head I don't think it will be a problem as long as I don't start hammering every module/component/library onto the global scope but I'm interested for someone more experienced to weigh in.
If I'm doing it wrong or there is a better way let me know!
Thanks
EDIT: After reading your comments, I realized your question has two components. One component is purely syntactical - and my original answer addresses a possible solution to that component. However, to the extent that your solution also incorporates an application design component, whereby dependencies of individual modules are defined at the application level, my answer is that is "bad practice." Dependencies should be defined at the module level (or lower) so that every element of your application is decouplable. This will be an enormous benefit in writing code that is (1) reusable, (2) testable, (3) refactorable. By defining your dependencies at the application level, you force yourself into loading the entire application simply to access a single module, for example, to run a test - and you inhibit rearranging modules or reuse of the same modules in other projects. If you do this, in the long run... you're gonna have a bad time.
Now, to the extent that your question is syntactical...
ORIGINAL ANSWER: I agree that require.js syntax is ugly as hell, annoying to use, and a potential source of difficult to debug errors. My own approach was to implement the following wrapping function (pardon the coffeescript).
# Takes dependencies in the form of a hash of arguments with the format
# { path: "name"}
# Assigns each dependency to a wrapper variable (by default "deps"),
# without the need to list each dependency as an argument of the function.
PrettyRequire = (argHash, callback) ->
deps = new Array()
args = new Array()
#loops through the passed argument, sorts into two arrays.
for propt of argHash
deps.push(propt)
args.push(argHash[propt])
#calls define using the 'dependency' array
define(deps, ()->
deps = new Array()
# assigns the resulting dependencies to properties of the deps object
for arg, i in args
deps[arg] = arguments[i]
# runs callback, passing in 'deps' object
return callback(deps)
)
This code is a simple rewrite which (1) preserves scope and (2) prettifies the syntax. I simply include the function as a part of a internal library that I maintain, and include that library at the outset of any project.
Define can then be called with the following (imho) prettier syntax:
PrettyRequire({
'backbone': 'Backbone'
'vendor/marionette': 'Marionette'
'../js/app/module': 'myModule'
}, (deps)->
Backbone.Model.extend(...) # for dependencies that assign themselves to global
deps.myModule(...) # for all dependencies (including globals)
)
That's my approach, to the extent that it's responsive to your question. It's worth noting also that your apps will take a (small) performance hit as a result of the increased overhead, but as long as you're not calling too many sub modules, it shouldn't be too much of an issue, and to me, it's worth it not to have to deal with the double syntax you describe.

Knockout Components "Uses require, but no AMD loader is present"

I am currently working in a Durandal project and researching the use of Knockout Components in my application. I'm building using Gulp and the gulp-durandal plugin and have it configured to use almond.
I'm running into an issue where I receive the following error when navigating to one of my pages which uses the newly registered components:
component: function () { return componentBindingValue; }" Message:
Component 'myComponent': Uses require, but no AMD loader is present
In the hopes of providing as much information as possible, here is the gulpfile I am currently using as well.
var gulp = require('gulp');
var durandal = require('gulp-durandal');
gulp.task('durandal', function() {
durandal({
baseDir: 'app',
main: 'main.js',
output: 'main-built.js',
almond: true,
minify: true,
rjsConfigAdapter: function (rjsConfig) {
rjsConfig.paths = {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'transitions': '../Scripts/durandal/transitions',
'dataservice': 'domain/dataservice'
};
return rjsConfig;
}
}).pipe(gulp.dest('build'));
});
The Durandal Gulp task is calling r.js with the wrap parameter configured to encapsulate your application code in an IFFE with the Almond source. Unfortunately, Almond's require, requirejs, and define implementations are getting bundled inside and not being added to the global window scope the way Knockout is expecting.
You can manipulate the wrap parameter in the rjsConfigAdapter to remove the IFFE wrappers, or just add require/define to the window object first thing in your application code to get around this.
Ex.
requirejs.config(config);
window.require = require;
window.requirejs = requirejs;
window.define = define;
I ran into this as well, but I had a much simpler front-end stack, and I was only seeing it on one page, even though I was using the component several places through my site.
Turns out it can also be a race condition. I had to put my ko.applyBindings inside of document.ready callback, and everything worked.

using knockout es5 plugin in AMD module

I can use knockout with requirejs applications AMD module, here example. Actually using dojo toolkit.
// Main viewmodel class
define(['knockout-x.y.z'], function(ko) {
return function appViewModel() {
this.firstName = ko.observable('Bert');
};
});
But how can I use knockoutjs extension knockout-es5 this is does not work in AMD module.
You are right, knockout-es5 is not an AMD-compliant module. However, that doesn't stop you from using it properly with RequireJS. What you will have to do is use a shim (http://requirejs.org/docs/api.html#config-shim) in your require config. The reason you need this is because you need to tell require that the plugin depends on Knockout and therefore cannot be loaded first.
Here is what your require config might look like:
var require = {
paths: {
'ko': 'knockout.min',
'koES5': 'knockout-es5.min',
},
shim: {
'koES5': { deps: ['ko'] }
}
};
This creates 2 paths, one to Knockout and one to Knockout-es5. In the shim we tell require that knockout-es5 depends on knockout. This ensures loading happens in the right order.
Now your RequireJS module you should look like:
define(['ko', 'koES5'], function(ko) {
return function appViewModel() {
this.firstName = ko.observable('Bert');
};
});
NOTE: You technically don't need ko as a requirement because koES5 will already have asked it to load since it is marked as a dependency. Also, you don't need a variable for koES5 because it doesn't return anything that you would use, you're simply adding it as a requirement to ensure the file gets loaded.

What's the correct way to expose a requireJS module to the global namespace?

I want to expose a Javascript API as a standalone library without polluting their global namespace. I've created the wrapper so I don't pollute their own requireJS according to http://requirejs.org/docs/faq-advanced.html. I've simplified what I have so far as below, but I'm not sure if this is the correct way or if I should be doing it some other way.
var MyApi = MyApi || {};
var MyApiRequireJS = (function() {
// require.js pasted here
return {requirejs: requirejs, require: require, define: define};
})();
(function(require, define, requirejs) {
require.config({
baseUrl: 'js/scripts',
waitSeconds: 30,
});
define( 'myapi', ['jquery', 'underscore'],
function($, _) {
$.noConflict(true);
_.noConflict();
function api(method, args, callback) {
// do stuff here
}
return {api: api};
}
);
require( ['myapi'], function( myapi ) {
MyApi = myapi;
});
}(MyApiRequireJS.require, MyApiRequireJS.define, MyApiRequireJS.requirejs));
Sites using this library would include a script tag referencing the above code and then call the api using
MyApi.api('some_remote_method', {foo: 'bar'}, function(result) {
// handle the result
});
I think you're trying to anticipate someone else's problem by making it your problem, but I don't think you can really reasonably do that. The page that you link to is designed to let people who already have Javascript globals named "require" or "define" rename the RequireJS globals to something different. It's not intended to create two separate RequireJS instances that independently resolve dependencies.
That said, if you are really trying to minimize the namespace pollution, then you should expose exactly one name -- MyApi. Write one monster closure that includes your private copy of RequireJS as well as your API code, and have it return only the methods you want to expose on your API.
It's probably much friendlier/simpler to deliver your API in two versions, one that defines a requireJS module, and one that has no requireJS dependency.

Categories

Resources