I'm looking to open source an angular directive on npm and I'm trying to come up with the most universal pattern for doing so. How's this? I have 3 questions:
!function(name, make) {
make = make()
// 1. Is this line needed?
var angular = require('angular')
// 2. Is this line needed?
angular.module(name, []).directive(name, make)
if (typeof module != 'undefined') module.exports = make
else this[name] = make
// 3. Is this line needed?
if (typeof define == 'function') define(function() { return make })
}('exampleDirective', function() {
return function() {
return {
link: function (scope, label, atts) {}
}
}
});
Is require('angular') needed or is it safe to assume the angular variable exists?
Is it necessary to call angular.module and angular.directive in my definition or should only the consuming app(s) do this?
Do AMD environments need this or does the module.exports or global suffice?
1
// 1. Is this line needed?
var angular = require('angular')
No. Applications that use your library must always import their own version of AngularJS.
2
// 2. Is this line needed?
angular.module(name, []).directive(name, make)
Yes. Applications will need to list your module name in their list of dependencies like this:
var myApp = angular.module('myApp',[name]);
3
// 3. Is this line needed?
if (typeof define == 'function') define(function() { return make })
}('exampleDirective', function() {
return function() {
return {
link: function (scope, label, atts) {}
}
}
});
No. You can just put the directive on the module and other developers will be able to use it.
Related
Is there a way to create a javascript micro-library (a library that has no dependencies), that support all of the following module formats:
Asynchronous Module Definition
CommonJS
exposing the library's exports as a global namespace object (no loader)
Yes, and I owe this answer to ded and his awesome modules:
(function(name, definition) {
if (typeof module != 'undefined') module.exports = definition();
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
else this[name] = definition();
}('mod', function() {
//This is the code you would normally have inside define() or add to module.exports
return {
sayHi: function(name) {
console.log('Hi ' + name + '!');
}
};
}));
This can then be used:
in AMD (e.g. with requireJS):
requirejs(['mod'], function(mod) {
mod.sayHi('Marc');
});
in commonJS (e.g. nodeJS):
var mod = require('./mod');
mod.sayHi('Marc');
globally (e.g. in HTML):
<script src="mod.js"></script>
<script>mod.sayHi('Marc');</script>
This method needs to get more publicity - if jQuery and co. started using it life would be much easier!
Here is a list of various cross-compatible module formats.
I suspect that the one you're looking for is what they're calling "commonjsStrict.js"
uRequire, the Universal Module & Resource Converter is the tool that does exactly that.
It mainly converts AMD and CommonJS to UMD / AMD / CommonJS / Plain script (no AMD loader required).
It allows declarative exporting of modules, with a noConflict() baked in.
It can manipulate modules (inject/replace/remove dependencies OR code) as you build them.
It converts from coffeescript, coco, Livescript, icedCoffeescript and you can add your own conversions in one liners!
Just to update a little bit on this answer in regards to #marc I too give credit to ded and have updated it a bit to be with the latest updates:
(function (name, definition, context, dependencies) {
if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); }
else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); }
else { context[name] = definition(); }
})('events', function () {
// Insert code here
return {
sayHi: function(name) {
console.log('Hi ' + name + '!');
}
};
}, (this || {}));
Object at the end is a reference to either the parent or the current scope, lets say you have a package you are writing and this is just a piece of the pie, well that context could be a name-spaced object and this is just a slice of that pie.
Also, if you wish to have dependencies, there is an optional parameter at the end after your scope which supports an array, in this case the definition parameter then can utilize each dependency as a argument. Also, the dependencies listed in an array will be required inside node-js platform for your convenience sake.
See: https://gist.github.com/Nijikokun/5192472 for a real example.
I have solved this exact problem and managed to easily support:
Dojo AMD (referencing the RequireJS specs)
jQuery (under $/jQuery.fn.[your_library_here])
node.js using vanilla require('path_to.js')
Browser window.[your_library_here]
It's using a combination of dependency injection and IIFE to get the job done.
See Below:
/*global jQuery:false, window:false */
// # A method of loading a basic library in AMD, Node.JS require(), jQuery and Javascript's plain old window namespace.
(function(exporterFunction) {
exporterFunction('cll',
function(a,b) {
return a+b;
}
);
})(
(function() { // Gets an exportFunction to normalize Node / Dojo / jQuery / window.*
if ((typeof module != 'undefined') && (module.exports)) { // Node Module
return function(library_name,what_was_exported) {
module.exports = what_was_exported;
return;
};
}
if (typeof define != 'undefined' && define.hasOwnProperty('amd') && define.amd) { // Dojo AMD
return function(library_name,what_was_exported) {
define(function() {
return what_was_exported;
});
};
}
if (typeof jQuery === 'function') { // jQuery Plugin
return function(library_name,source) {
jQuery.fn[library_name] = source;
return;
};
}
if (typeof window != 'undefined') { // Fall down to attaching to window...
return function(library_name,what_was_exported) {
window[library_name] = what_was_exported;
};
}
})(),
(function() {
// ## Other Parameters Here
// You could add parameters to the wrapping function, to include extra
// functionalilty which is dependant upon the environment... See
// https://github.com/forbesmyester/me_map_reduce for ideas.
return 'this_could_be_more_arguments_to_the_main_function';
})()
);
Public Gist available at https://gist.github.com/forbesmyester/5293746
This is based on Nijikokun's answer. Since RequireJS discourages the use of explicit module names this has been omitted in this version. The second argument to the loader describe the dependencies. Pass [] if you don't need to load any.
var loader = function(name, dependencies, definition) {
if (typeof module === 'object' && module && module.exports) {
dependencies = dependencies.map(require);
module.exports = definition.apply(context, dependencies);
} else if (typeof require === 'function') {
define(dependencies, definition);
} else {
window[name] = definition();
}
};
loader('app', ['jquery', 'moment'], function($, moment) {
// do your thing
return something;
}
I've posted a solution to github that should work with any browser. Its based on how jQuery and underscore.js implemented their libraries but added a private anonymous object allowing you to use this to reference its private namespace; making for easy automated extension and scoping. Of course it has an _export object to expose your public APIs.
I also posted sample of how to define an internal library that has similar private scope and public exports.
My code is posted here: https://github.com/JavaScriptDude/JSLibraryTemplate
Note: I have not tested outside of the browser but it should work in node.
My team wants to move to a style of dependency injection that is closer to the CommonJS/Node JS syntax for out Angular codebase:
var myDependency = require('myDependency');
I've started using $injector.get() directly inside at the top of my functions, so far with no obvious trouble. That means I've converted this:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['serviceA', 'serviceB', 'serviceC', function(serviceA, serviceB, serviceC) {
//My code here
}])
Into:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['$injector', function($injector) {
var serviceA = $injector.get('serviceA');
var serviceB = $injector.get('serviceB');
var serviceC = $injector.get('serviceC');
//My code here
}]);
Is there something I'm missing. Does not declaring the required dependencies outside of the function definition cause any sort of performance issues?
Note: this answer has not been tested.
After digging into the angular's code, I find this:
// in function createInjector
function provider(name, provider_) {
...
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
...
return providerCache[name + providerSuffix] = provider_;
}
...
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
...
// in function createInternalInjector
function invoke (...){
...
for (i = 0, length = $inject.length; i < length; i++) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
...
return fn.apply(self, args);
}
function instantiate(...){
...
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
...
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
Its calls seems like:
service invoked factory invoked instantiate
factory invoked provider invoked instantiate
So I think your questions is equal to Is calling $injector.get() one by one has the same performance as calling $injector.instantiate() once?
As the code shows, instantiate invoked invoke which actually invoked getService for each services injected by you. And $injector.get is just binding to getService.
So the answer to my equal question is True.
And the answer to your question is No, their performance is very close.
Please correct me if I'm wrong, thank you!
There are no significant difference between both pieces of code in the case of a service. $injector.get(...) call doesn't provide any overhead. But it provides a significant amount of extra characters per dependency.
There is a difference when the same thing is done with injectables that have local dependencies - controllers and route/state resolvers.
When these dependencies
app.controller('SomeCtrl', function ($scope, service) { ... });
are replaced with $injector.get(...), it will choke on $scope - it is local dependency. And with other dependencies being retrieved like that, controller loses its testability. The dependencies cannot be mocked with
$controller('SomeCtrl', { service: mockedService });
I personally don't see how $injector.get(...) may benefit the style of the project (and haven't seen a good style guide that would suggest it).
Node uses require function because it works for it, not because it is better or cooler. The alternative would be to pack each Node script into into AMD-like wrappers, which would be painful. Fortunately, we already have wrappers around Angular units!
Is it possible to retrieve a require function with a custom local context?
Same as requiring a require from a module, but i need to arbitrarily choose the base path:
as this pseudo:
var custom_local_require = require.local('/base/url/');
custom_local_require('./path/to/module'); // will return module `/base/url/path/to/module.js`
[EDIT]
digging in requirejs code i found these
var ctx = requirejs.s.newContext(contextName);
ctx.makeRequire(relMap, options);
are requirejs.s.* safe apis ?
any documentation?
meanwhile i ended up with this..
function localrequire(baseurl) {
function path_relative_to(path) {
if (path.startsWith('.'))
return baseurl + path;
else
return path;
}
return function(deps, cb, eb) {
if (Array.prototype.isPrototypeOf(deps)) {
deps = deps.map(path_relative_to);
return require(deps, cb, eb);
} else {
var _local_path = path_relative_to(deps);
return require(_local_path);
}
}
}
var custom_local_require = localrequire('/base/url/');
custom_local_require('./path/to/module'); // will return module `/base/url/path/to/module.js`
seems to work but any suggestion and bug-smell is appreciated!
Got it!
as from docs there is a Multiversion Support for the purpose:
just call require.config with a context property, it returns an isolated require function with that configuration.
so, to solve my problem:
require.config({
baseUrl:'/mainbase/'
});
var _ctx_config = {
baseUrl:'/base/url/'
// any other config may be added here
// they will be kept isolated
};
var custom_local_require = require.config(_ctx_config);
custom_local_require('path/to/module'); // will return module `/base/url/path/to/module.js`
require('path/to/module'); // will return module `/mainbase/path/to/module.js`
When defining an Angular module I define what are my dependencies like this:
var myModule = angular.module("MyModuleName", ["Dep1", "Dep2", "Dep3"]);
Each dependency has its own dependencies, directives, controllers etc.
Is there a way to ask AngularJS what are the available injectables? Without instantiating them, just getting a list.
The Angular.$injector only has a "get" method, but this means that I'll instantiate it.
Thanks
Gil Amran
Actually that's kind of a hack , so only use it for testing and learning purposes!!
All the $injector DI magic is kept inside this file:
https://github.com/angular/angular.js/blob/v1.2.7/src/auto/injector.js
see the createInjector function (line 598 : angularjs 1.2.7)
The providerCache variable is the container off what's available to $injector in config blocks.
The instanceCache variable is the container off what's available to $injector in all other blocks.
function createInjector(modulesToLoad) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap(),
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() {
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider);
}));
Those variables are well encapsulated inside that closure and you cannot get them from outside.
unless you add these two lines to the createInjector function:
window.providerCache = providerCache;
window.instanceCache = instanceCache;
How to use it:
Download the source code from here: http://code.angularjs.org/1.2.7/angular.js
Add these 2 lines at line 3549
In my previous question I got an answer hot to inject dependencies and while I tested it, everything worked well. Then I refactored the code and wanted to start the app implementation, but the injections stopped to work :(
http://jsbin.com/alemik/1/edit
In addittion to jsbin, here is the source:
var ABCS = angular.module("ABCS", []);
ABCS.Configuration = angular.module('ABCS.Configuration', []);
ABCS.Controllers = angular.module('ABCS.Controllers', []);
ABCS.Modules = angular.module("ABCS.Modules", []);
ABCS.Services = angular.module("ABCS.Services", []);
ABCS.run(["$rootScope", "$location", "ABCS.Services.LogService",
function ($rootScope, $location, logger) {
logger.log("app start");
}]);
ABCS.Configuration.factory("Environment", function () {
return {
logOutputElementId: "output",
hasConsole: console && console.log
}
});
//Log service
ABCS.Services.LogService = function (config) {
this.log = function (message) {
if (typeof (config.Environment.logOutputElementId) === "string") {
document.getElementById(config.Environment.logOutputElementId).innerHTML += message + "<br/>";
}
else if (config.Environment.hasConsole) {
console.log(message);
}
else {
alert(message);
}
};
};
ABCS.Services.LogService.$inject = ["ABCS.Configuration"];
ABCS.Services.factory("ABCS.Services.LogService", ABCS.Services.LogService);
What I miss? Why ABCS.Services.LogService can not be injected in current structure.
Thanks
When you've got several modules, you need to make sure that you declare your modules' dependencies. This tells the dependency injector to look in those modules when looking for providers.
var ABCS = angular.module("ABCS", [
'ABCS.Services', 'ABCS.Configuration', 'ABCS.Controllers',
'ABCS.Modules', 'ABCS.Services'
]);
I had to make a few more adjustments to get it working:
The dependency injector won't inject an entire module, and so ABCS.Services.LogService.$inject = ["ABCS.Configuration"]; wasn't working. I've changed that to ABCS.Services.LogService.$inject = ["ABCS.Configuration.Environment"]; and adjusted the associated factory in order to fit your naming conventions.
factory accepts a function, but won't call it as a constructor, and thus using this in your definition of LogService won't act as you are expecting it to. I've changed your factory function to define a constructor function, which is then instantiated and returned.
See a working version here: http://jsbin.com/alemik/2/edit
In my opinion , that's the angularJS way of doing things ( though i kept all the DI definitions ):
angular.module("ABCS", ['ABCS.Services', 'ABCS.Configuration', 'ABCS.Controllers', 'ABCS.Modules', 'ABCS.Services']);
angular.module('ABCS.Configuration', []);
angular.module('ABCS.Controllers', []);
angular.module("ABCS.Modules", []);
angular.module("ABCS.Services", []);
angular.module("ABCS").run(["$rootScope", "$location", "ABCS.Services.LogService",
function ($rootScope, $location, logger) {
logger.log("app start");
}]);
angular.module('ABCS.Configuration').factory("ABCS.Configuration.Environment", function () {
return {
logOutputElementId: "output",
hasConsole: console && console.log
};
});
angular.module("ABCS.Services").factory("ABCS.Services.LogService",["ABCS.Configuration.Environment",function (environment) {
function LogService() {
this.log = function (message) {
if (typeof (environment.logOutputElementId) === "string") {
document.getElementById(environment.logOutputElementId).innerHTML += message + "<br/>";
}
else if (environment.hasConsole) {
console.log(message);
}
else {
alert(message);
}
}
}
return new LogService();
}]);
angularJS allows to reopen a module definition in multiple files , and javascript namespacing is totally unecessary unless the object is not tight to the angularJS application.
Mixing javascript namespacing and DI namespacing makes code more error prone ,not more maintanable.