Factory without inject the dependency - angularjs

This is my code that currently works:
angular.module('myApp')
.controller('myCtrl', function (DocumentTypeManagerPdf, DocumentTypeManagerVideo) {
$scope.showPreview = function(document){
var previewModule = eval('DocumentTypeManager' + document.clientModule);
previewModule.show(document);
};
});
but... two things I would avoid:
Eval is evil
I am forced to inject every DocumentTypeManagerXYZ that I'll implement
In there a better solution tu use a Factory dynamically?

I think you should go with a factory pattern.
One service DocumentTypeManagerFactory
With one method like
var myDocumentTypeManager = DocumentTypeManagerFactory.instanciateWithType(document.clientModule);
myDocumentTypeManager.show(document);
Your controller will only inject one service (and the DocumentTypeManagerFactory should inject all)
In your DocumentTypeManagerFactory you should make a switch or if/else to avoid eval.

I think you can use arguments in the function. inJS every function has a variable named arguments which is a array of given parameters.
But I am not sure how your DocumentTypeManagerXYZ objects are structured. So just type debugger; beginning of your controller function and check arguments data by console then you can take a correct action.
the below one is the first one comes to my mind;
var previewModule;
for(var i = 0, len=arguments.lengh; i <len; i++) {
if (arguments[i].constructure.name === 'DocumentTypeManager' + document.clientModule) {
previewModule = arguments[i];
break;
}
}
this will be your basic approach.
as this is an angular application you can user $injector.get("moduleName")
for example;
var previewModule = $injector.get("'DocumentTypeManager' + document.clientModule");
please see $injector

Related

Integrating helper functions in AngularJS

So, I've just began learning Angular and my question is what ways (or the best practices) are there for injecting helper functions into AngularJS? I often need to include functions to assist my controller, but I have read online that the controller should hold as little logic as possible, which means they should be injected into the controller and declared in the module (fat module, skinny controller).
As such, I have been primarily injecting functions like this:
$provide.value
$provide.value('MySQLtoJS', function(datetimeString) {
var t = datetimeString.split(/[- :]/);
var d = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
return d;
});
This uses the $provide service to create a value that can be injected in my controller. However, for more elaborate functions, such as those that require an injectable, I have been using this:
Factory provider
.factory('convertMySQLToJS', ['moment', function(moment) {
return function(arrayInput) {
if (Array.isArray(arrayInput)) {
for (var i = 0; i < arrayInput.length; i++) {
var t = arrayInput[i].begin_datetime.split(/[- :]/);
var start = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
arrayInput[i].begin_datetime = start;
var t = arrayInput[i].end_datetime.split(/[- :]/);
var end = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
arrayInput[i].end_datetime = end;
//Also create the moment message
if (arrayInput[i].begin_datetime >= new Date()) {
arrayInput[i].message = 'Begins at ' + moment(arrayInput[i].begin_datetime).format('MMMM Do YYYY, h:mm a') + ' and likely ends at ' + moment(arrayInput[i].end_datetime).format('MMMM Do YYYY, h:mm a');
}
else {
arrayInput[i].message = 'Began at ' + moment(arrayInput[i].begin_datetime).format('MMMM Do YYYY, h:mm a') + ' and likely ended at ' + moment(arrayInput[i].end_datetime).format('MMMM Do YYYY, h:mm a');
}
}
return arrayInput;
}
}
}])
However, factories are often used for their service and properties (like $http), or so I've been told. So I've been recently suggested by some people that I should be including them in the run configuration block (which seems a bit weird to me) by using $rootScope and giving it that property for the function I need. Since I'm new to Angular and I've found documentation rather lacking, I'm wondering how am I supposed to inject helper functions correctly into Angular, if there is a correct way?
The correct way in Angular is to wrap helpers into services and inject them when needed. It is a good idea to join several similarly themed methods into single helper service (think of it as of utility class).
It can be either factory, or value, or constant. The latter is preferable for such things because it can be also used within config blocks. They are interchangeable in other respects, as long as the factory function consists of return statement and doesn't use other dependencies. Since this one
app.factory('mysqlHelper', function (moment) {
return {
MySQLtoJS: function(datetimeString) { ... },
convertMySQLToJS: return function(arrayInput) { ... }
};
});
uses moment dependency, factory is the case for it.
Using globals (either on global JS scope or $rootScope) is considered bad practice:
Of course, global state sucks and you should use $rootScope sparingly,
like you would (hopefully) use with global variables in any language.
In particular, don't use it for code, only data. If you're tempted to
put a function on $rootScope, it's almost always better to put it in a
service that can be injected where it's needed, and more easily
tested.
And thus it provides a reasoning for that: testability. Services are testable. They can be unit-tested, they can be mocked. That is where Angular dependency injection shines.
Factories/services are certainly a way to do something like this. If it is always related to service activities such as sanitizing your data after retrieval, placing it in a service (or base service) works just fine. However, I find that always injecting services is a bit heavy when I have one-off helper functions that I need to pass around my app. I've ended up placing a special object on angular that holds my helper functions.
app.run([function() {
angular.UTIL = angular.UTIL || {};
var util = {
coolFunction: function(fieldName) {
return fieldName;
}
}
angular.extend(angular.UTIL, util);
}]);
This can then be called throughout your app:
var getField = angular.UTIL.coolFunction("fieldName");
As far as best practice goes with helpers such as these, it is best if you follow a few rules:
The helper functions are global and can easily be used throughout the codebase in a variety of applications and patterns
They cannot be easily translated into a directive and do not directly manipulate the DOM
They are helpers, not functionality or business logic
They replace utility functions that have been copied into multiple files
They are not bound to a specific scope

How are the local variables inside the anonymous function after a DI set?

I have read as much as I could from tutorials and the Angular docs but I still have a few questions about what is going on behind the scenes. I think my main questions are in regards to dependency injections... when you declare them in the first argument of the array, are the services called? How are the values passed along to the anonymous function?
For example:
This is my value:
angular.module("root", [])
.value("message", "Hello world!");
And this is my controller:
angular.module("root", [])
.controller("index", ["$scope", "message", function($scope, message) {
// Do something with message and/or $scope.
}]);
So my question is this:
When we declare a dependency injection in the index controller in the 1st argument of the array... what is going on? I know $injector:
is responsible for actually creating instances of our services using the code we provided via $provide (no pun intended). Any time you write a function that takes injected arguments, you're seeing the injector at work.
Once you have $injector, you can get an instance of a defined service by calling get on it with the name of the service.
Here is the quote I am confused about:
The injector is also responsible for injecting services into functions; for example, you can magically inject services into any function you have using the injector's invoke method;
What does injecting a service into a function mean behind the scenes? Is some function called when we declare the strings in the array and are the return values set to be the value of the local variables inside the anonymous function?? How is message set?
function($scope, message) {...
Here's another example.
So this factory is dependent on the factor value:
angular.module("services", [])
.value("factor", 6)
.factory("square", ["factor", function (factor) {
return factor * factor;
}]);
And this controller depends on $scope and square services:
angular.module("root", ["services"])
.controller("index", ["$scope", "square",
function ($scope, square) {
$scope.product = square;
}
]);
But how are the local variables inside the anonymous function set?
Questions:
Note: I read this already:
dependency injection
To answer your question: 'But how are the local variables inside the anonymous function set?' : they're not, they're provided as arguments. They are 'injected' into the function.
Take a look at this example:
inject = function(first, second, target) {
return target(first, second);
}
greeter = function(name, age) {
var name = name;
var age = age;
return {
sayHi : function() {
console.log('Hi there ' + name + ' you are ' + age);
}
}
}
var johngreeter = inject('john',22,greeter);
johngreeter.sayHi(); // Hi there john you are 22
var janegreeter = inject('jane',30,greeter);
janegreeter.sayHi(); // Hi there jane you are 30
You can play around with it here: https://jsfiddle.net/gc5066x2/
I hope you see the resemblence with your Angular code. The .controller function has a name (first arg), then an array of parameters and then the actual controller function (second arg).
The "$scope", "square" will be injected into the $scope, square parameters of the function in the 3rd part of that array. So what I suspect you call 'local variables' $scope, were set by the injector as function arguments.
The Angular injector is much more sophisticated obviously, and it will try to resolve $scope and square by name or some other convention, but I hope you get the point: it injects ( hence a good name ) the arguments of the function.

adding new property to an object exposed to scope in angularjs

I have an object that is exposed to $scope and has properties attached to it.
I access this object in my controller and call a service which does little something.
What I need to add to it is new property to the object(which is going to be an array attached to the object as a property) so that updated object is returned to the controller and I can access the elements of this array using data-expression tag{{ }}.
I would like to know in detail about making such manipulations the right way and possible ways of doing it.
Just add the array to the object.
$scope.myobj = {};
...
// sometime later
var numbers = [1,2,3];
$scope.myobj.numbers = numbers;
EDIT:
To answer your question about scope in a service. Scope is not accessible in a service. Typically you ask your service for something IE data. But your service can do anything, like add 2 numbers, or in your case create an array of something that you need to attach to your object.
module.service('MyService', function() {
this.add = function(number1, number2) {
return number1 + number2;
}
this.createMyArray = function() {
// pass in whatever you need to this service in order
// to create the array you need.
// example of just returning a hard coded array
return [1,2,3];
}
});
Then you can inject your service into your controller which has the scope you want to modify.
app.controller('MyController', function($scope, MyService) {
$scope.add = function(number1, number2) {
// Lets use our service to add these numbers and
// assign result to scope variable
$scope.answer = MyService.add(number1, number2);
}
$scope.myobj = {};
$scope.makeArray = function() {
// lets use our service again to create an array
// this time lets add it to a scope object that already exists.
$scope.myobj.numbers = MyService.createMyArray();
}
});
A lot of times services are used to grab/update things from a server, so you will see a lot of examples that make http get/post/put/delete calls. Then from your controller(s) you can use those services to grab data. But again you are not limited to that, your service can simple just hold some static data or helper functions too.

How to prevent a directive from binding to elements within a controllers scope in Angular?

I have an Angular app, MyApp, that depends on external modules (two different map solutions), and I need them both but in different controllers (different modules within MyApp even).
The problem is the two modules both have directives that bind to the same argument ('center' in this case), which causes them both do manipulate a single element. What I want is for one directive to be active inside one controller and the other directive to be active inside another controller - so not have them inpact my elements at the same time.
I don't want to change the code of the external modules to achive this.
I found this to be a very interesting question. The answer below is incomplete, and, frankly, a bit hackish, but it demonstrates a way to rename a directive in another module without modifying the source of the module itself. There is a lot of work to do to make this anywhere near production ready and it absolutely can be improved.
The caveats to the solution are that once a directive is renamed, the "old" name will no longer work. It also depends on some angular conventions that might be changed with future versions, etc, so it's not future proof. It also might fail for complex directives, and I haven't really done any testing on it.
However, it demonstrates that it can be done, and the concept might lead to a feature angular needs (the ability to namespace external modules in order to prevent conflicts such as the one your are experiencing).
I think that if your use case is fairly simple, this will solve your problem, but I wouldn't recommend using it in the general case yet.
(function () {
var util = angular.module('util', [], function ($compileProvider) {
util.$compileProvider = $compileProvider
})
.factory('$directiveRename', function () {
var noop = function () { return function () { }; };
return function (module, directive, newDirective) {
var injector = angular.injector(['ng', module]);
var directive = injector.get(directive + 'Directive');
if(directive)
{
//there can be multiple directives with the same name but different priority, etc. This is an area where this could definitely be improved. Will only work with simple directives.
var renamedDirective = angular.copy(directive[0]);
delete renamedDirective['name'];
util.$compileProvider.directive(newDirective, function () {
return renamedDirective;
});
}
//noop the old directive
//http: //stackoverflow.com/questions/16734506/remove-a-directive-from-module-in-angular
angular.module(module).factory(directive + 'Directive', noop);
};
});
})();
Example usage:
angular.module('app', ['module1', 'module2', 'util'])
.run(function ($directiveRename) {
$directiveRename('module1', 'test', 'testa');
$directiveRename('module2', 'test', 'testb');
});
An alternative, slightly less hackish answer.
Add the following immediately after the script tag that includes angular (before any other modules are loaded)
<script type="text/javascript">
var angularModule = angular.bind(angular, angular.module);
angular.module = function (moduleName, requires, configFn) {
var instance = angularModule(moduleName, requires, configFn);
var directive = instance.directive;
instance.directive = function (directiveName, directiveFactory) {
//You can rename your directive here based on the module and directive name. I don't know the module and directive names for your particular problem. This obviously could be generalized.
if (instance.name == 'module1' && directiveName == 'test') {
directiveName = 'testa';
}
if (instance.name == 'module2' && directiveName == 'test') {
directiveName = 'testb';
}
return directive(directiveName, directiveFactory);
}
return instance;
};
</script>
This works by intercepting calls to module.directive and allowing you the opportunity to rename the directive before it is created.

How to pass a lambda expression to an AngularJS directive

I'm trying to create a set of AngularJS directives that will process an array of objects and perform specific operations using either the objects themselves or perhaps a property or sub-property of the each instance.
For example, if the array contains strings, one such directive might render a comma-separated list of those strings. I anticipate using such a directive like this:
<csv-list items="myArray" />
However, as stated above, I want the implementation to be flexible enough to pass an array of objects to the directive, whereby the directive can be instructed to act on a specific property or sub-property of each instance. If I could pass a lambda expression to the directive, I would imagine using it something like this:
<csv-list items="myArray" member="element => element.name" />
I guess there's a recommended AngularJS pattern to solve such problems, but I am quite new to AngularJS, so I haven't found it yet. Any suggestions would be appreciated.
Thanks,
Tim
There are several ways to do this, Using the $parse service may be the easiest
var parser = $parse("name");
var element = {name:"thingA"};
var x = parser(element);
console.log(x); // "thingA"
Parse has been optimized to act quickly in these scenarios (single property look-ups). You can keep the same "parser" function around and invoke it on each element.
You could also split on the '.' and do the simple look-up yourself (reading in 'member' to your directive as a string), in simple form:
var paths = myPath.split('.');
var val = myObj;
for(var i = 0; i < paths.length; i++){
val = val[paths[i]];
}
return val;
There are also various linq-like libraries that support lambda expressions as strings (linqjs, fromjs). If you've gotta have a fat arrow function.
Your directive can look at other attributes, so you could add a property-name attribute and have your directive manually check that property. To be fancy you could use $parse like ng-repeat does to parse an expression.
<csv-list items="element in myArray" member="element.name">
Another way would be to create a 'property' filter that takes an array of objects and returns an array of property values from that object that you could use like so:
<csv-list items="myArray|property:name">
Here's what you're asking for syntactically (Show me the code - plunkr):
member="'e' |lambda: 'e.name'"
You can do this with something like (I wrote this just for the question, what I do in my apps is outlined below)
app.filter('lambda', [
'$parse',
function ($parse) {
return function (lambdaArgs, lambdaExpression, scope) {
var parsed = $parse(lambdaExpression);
var split = lambdaArgs.split(',');
var result = function () {
var args = {};
angular.extend(args, scope || {});
for (var i = 0; i < arguments.length && i < split.length; i++) {
args[split[i]] = arguments[i];
}
return parsed(args);
};
return result;
}
}
]);
Advanced usage:
(x, y, z) => x * y * z + a // a is defined on scope
'x,y,z' |lambda: 'x * y * z + a':this
The :this will pass the scope along to the lambda so it can see variables there, too. You could also pass in an aliased controller if you prefer. Note that you can also stick filters inside the first argument to the lambda filter, like:
('x'|lambda:'x | currency')(123.45) // $123.45 assuming en-US locale
HOWEVER I have thus far avoided a lambda filter in my apps by the following:
The first approach I've taken to deal with that is to use lodash-like filters.
So if I have an array of objects and your case and I want to do names, I might do:
myArray | pluck:'name'
Where pluck is a filter that looks like:
angular.module('...', [
]).filter('pluck', [
function () {
return function (collection, property) {
if (collection === undefined) {
return;
}
try {
return _.pluck(collection, property);
} catch (e) {
}
}
}
]);
I've implemented contains, every, first, keys, last, pluck, range (used like [] | range:6 for [0,1,2,3,4,5]), some, and values. You can do a lot with just those by chaining them. In all instances. I literally just wrapped the lodash library.
The second approach I've taken is to define functions inside a controller, expose them on the scope.
So in your example I'd have my controller do something like:
$scope.selectName = function (item) { return item.name };
And then have the directive accept an expression - & - and pass selectName to the expression and call the expression as a function in the directive. This is probably what the Angular team would recommend, since in-lining in the view is not easily unit-test-able (which is probably why they didn't implement lambdas). (I don't really like this, though, as sometimes (like in your case) it's strictly a presentation-thing - not a functionality-thing and should be tested in an E2E/Boundary test, not a unit test. I disagree that every little thing should be unit tested as that often times results in architecture that is (overly) complicated (imho), and E2E tests will catch the same thing. So I do not recommend this route, personally, though again I think the team would.)
3.
The third approach I've taken would be to have the directive in question accept a property-name as a string. I have an orderableList directive that does just that.

Resources