Two AngularJS controllers with same logic - angularjs

I have two divs, each requiring a controller to handle activities within it. Both controllers have different names of course.
This situation is problematic since it requires maintenance on two identical pieces of code.
I was considering two options and am seeking comments:
Deploy the common code into a separate file and, using jQuery, include the file into both controllers,
Create a new file with a function that would receive the same parameters as the controllers and that would contain the whole logic currently hosted within both controllers. Then, simply invoke this function from within both controllers.
In the second option, the body of the controllers would become a single statement, i.e. the invocation of the new function.
From posts I found, it looks that the first option does work. My question is (before I engage into significant changes and testing): Would the second option work as well? and if yes, is it better, equivalent or worse than the first?
Edit
Though, as explained, the actual code is of no relevance to my question, I'm adding a set of sample pieces to illustrate my question, especially the second option.
So here is the initial arrangement:
Controller 1
myApp.controller("First_Controller", "$rootScope", "$scope" , [function($rootScope , $scope) {
$scope.Do_Something = function () {
// some code here
}
}
Controller 2
myApp.controller("Second_Controller", "$rootScope", "$scope" , [function($rootScope , $scope) {
$scope.Do_Something = function () {
// some code here
}
}
The idea is to convert this to:
Controller 1 - NEW
myApp.controller("First_Controller", "$rootScope", "$scope" , [function($rootScope , $scope) {
Common_Function($rootScope , $scope) ;
}
Controller 2 - NEW
myApp.controller("Second_Controller", "$rootScope", "$scope" , [function($rootScope , $scope) {
Common_Function($rootScope , $scope) ;
}
Where:
Common_Function = function ($rootScope , $scope) {
$scope.Do_Something = function () {
// some code here
}
}

Related

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.

AngularJS, load module

First of all, i'm discovering AngularJS. I read many courses about it but i'm far from being familiar with it.
I have a project, were i cannot modify the previous declarations.
I want to add wysiwyg into the project.
I have to create an other controller using the existant module.
I know that if i redefine the module, previous will be lost.
I thought this would be good :
angular.module('demo')
.controller('WysiwygCtrl', ['colorpicker.module', 'wysiwyg.module', function($scope) {
$scope.data = {
text: "hello"
}
}]);
But it doesn't work.
In fact, the easiest way would be :
angular.module('demo', ['colorpicker.module', 'wysiwyg.module'])
.controller('WysiwygCtrl', function($scope) {
$scope.data = {
text: "hello"
}
});
But it creates a new module and i loose previous one ...
How can i do to make it works ? If you need more code i can edit my question just ask but i think the module/controller is the most important part.
Thanks for you help, i'm facing this problem since this morning.
EDIT1 : The wysiwyg library is hosted on github here https://github.com/TerryMooreII/angular-wysiwyg
EDIT2 : Right now, nothing is displayed because i have the following error :
Error: [$injector:unpr] http://errors.angularjs.org/1.2.16/$injector/unpr?p0=colorpicker.moduleProvider%20%3C-%20colorpicker.module
angular.module('moduleName', ['dep1', 'dep2']) - creates a module, that has dependencies listed in a second parameter, this signature also returns newly created module, you HAVE to specify list of dependencies, even if it's just an empty array []. This also overwrites any existing modules by the same name.
angular.module('moduleName') - returns a module created earlier in your code, hence the absence of dependency list in the signature - this also returns a module.
both signatures allow you to add controllers, services, etc..
Plus I think you need to be passing in references to them modules in the function
.controller('WysiwygCtrl', ['$scope', 'colorpicker.module', 'wysiwyg.module', function($scope, colorpickermodule, wysiwygmodule) {
$scope.data = {
text: "hello"
}
}
If you use the array notation when creating components as controllers or directives, then the parameters on the main function or module should match. As i see that you use
.controller('WysiwygCtrl', ['colorpicker.module', 'wysiwyg.module', function($scope) {
$scope.data = {
text: "hello"
}
you maybe want to say
.controller('WysiwygCtrl', ['$scope', 'colorpicker.module', 'wysiwyg.module', function($scope) {
$scope.data = {
text: "hello"
}

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.

Factory without inject the dependency

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

Is it good practice to combine CREATE and EDIT controllers in AngularJS?

There are many duplicated code among CREATE and EDIT controllers.
These controllers could be combined into one for minimizing repetitive code.
The problem: I need to distinguish which method to use on form submitting - create() or edit() for example.
The solution: I could add $scope.mode for example and set $scope.mode='edit' if user clicked 'EDIT' button or set $scope.mode='add' if user clicked 'ADD' button.
I could use services for minimizing repetitive code, but there still will be duplicated code. For example in both controllers I have cancel() method which clears the form and hide it. I could store clearForm() and hideForm() in the service, but this code will be duplicated in both controllers:
$scope.cancel = function() {
Service.clearForm();
Service.hideForm();
};
Questions:
Is it good practice to combine CREATE and EDIT controllers in AngularJS?
Is there any good practices to minimize repetitive code?
Yes. Use 1 controller.
Here is the reason why use 1 controller
The job of the controller is to support the View. Your create view and the edit view is exactly same - just that one has data pre-populated (edit) and another does not (create).
Moreover the "purpose" of this View is to have the user change or enter new values in the form. Your only difference should be something like reset(). But even there you could start with an empty model object e.g. $scope.entity = {} in case of CREATE and you will start with $scope.entity = $http.get().
Repetition Problem with 2 Controllers
With 2 different controllers and services you are going to incur at least the following duplication:
$scope.cancel = function() {
Service.cancel();
};
$scope.validate = function() {
ValidtionSvc.validate();
}
.
.
.//other stuff similar
but the problem is why even this duplication like you stated.
(UDATED here onwards since above was the answer to the 1st question)
How to use 1 controller with repetition ?
Is there any good practices to minimize repetitive code?
Question redefined: Is there a good practice of eliminating repetitive code in CREATE and EDIT forms ?
No formal 'best practice' exist to my knowledge to avoid repetitive code in this specific situation. However I am advising against mode=edit/create. The reason being for controllers in this situation there should be almost no difference since their job is to purely to fetch/update the model as the user interacts.
Here are the difference you will encounter in this situation and how you can avoid if/then/else with mode=create/edit:
1) Populating the form with existing values vs. empty form for Create.
To fetch a existing entities you need some key/query data. If such key data is present you could do
var masterEntity = {};
if(keyData) {
masterEntity = MyEntityResourceFactory.getEntity(keyData);
}
$scope.entity = masterEntity;//for Create this would be {}
2) reset() form
should be simply
$scope.reset = function() {
$scope.entity = masterEntity;
}
3) Update/Create
$http.post()//should not be different in today's world since we are treating PUT as POST
4) Validation - this is a perfect reuse - there should be no differences.
5) Initial / Default Values
You can use masterEntity = Defaults instead of {}.
Is it good practice to combine CREATE and EDIT controllers in
AngularJS?
In my experience, yes it is a good idea for 99.9% of the time. I typically inject a formType variable into my controller via the $routeProvider resolve feature. So I would have something like the following:
$routeProvider
.when('/item/create', {
templateUrl: '/app/item/itemForm.html',
controller: 'itemFormController',
resolve: {
item: ['$route', 'itemRepository', function ($route, itemRepository) {
return itemRepository.getNew();
}],
formType: function () { return Enums.FormType.CREATE; }
},
})
.when('/item/edit/:itemId', {
templateUrl: '/app/item/itemForm.html',
controller: 'itemFormController',
resolve: {
item: ['$route', 'itemRepository', function ($route, itemRepository) {
return itemRepository.get($route.current.params.itemId);
}],
formType: function () { return Enums.FormType.EDIT; },
},
});
That way you get your entity and type of form action injected into the controller. I also share the same templates, so saving a form I can either rely on my repository/service to determine what REST endpoint to call, or I can do a simple check inside the controller depending on what formType was injected.
Is there any good practices to minimize repetitive code?
Some of the things I'm using to keep things DRY:
If you keep a common convention on your server API you can go a very long way with a base factory/repository/class (whatever you want to call it) for data access. For instance:
GET -> /{resource}?listQueryString // Return resource list
GET -> /{resource}/{id} // Return single resource
GET -> /{resource}/{id}/{resource}view // Return display representation of resource
PUT -> /{resource}/{id} // Update existing resource
POST -> /{resource}/ // Create new resource
etc.
We then use a AngularJs factory that returns a base repository class, lets call it abstractRepository. Then for each resource I create a concrete repository for that specific resource that prototypically inherits from abstractRepository, so I inherit all the shared/base features from abstractRepository and define any resource specific features to the concrete repository. This way the vast majority of data access code can be defined in the abstractRepository. Here's an example using Restangular:
abstractRepository
app.factory('abstractRepository', [function () {
function abstractRepository(restangular, route) {
this.restangular = restangular;
this.route = route;
}
abstractRepository.prototype = {
getList: function (params) {
return this.restangular.all(this.route).getList(params);
},
get: function (id) {
return this.restangular.one(this.route, id).get();
},
getView: function (id) {
return this.restangular.one(this.route, id).one(this.route + 'view').get();
},
update: function (updatedResource) {
return updatedResource.put();
},
create: function (newResource) {
return this.restangular.all(this.route).post(newResource);
}
// etc.
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
};
return abstractRepository;
}]);
Concrete repository, let's use customer as an example:
app.factory('customerRepository', ['Restangular', 'abstractRepository', function (restangular, abstractRepository) {
function customerRepository() {
abstractRepository.call(this, restangular, 'customers');
}
abstractRepository.extend(customerRepository);
return new customerRepository();
}]);
What you'll find if you use this base repository pattern is that most of your CRUD controllers will also share a lot of common code, so I typically create a base CRUD controller that my controllers inherit from. Some people dont like the idea of a base controller, but in our case it has served as well.
The answer to your first question probably depends on the specific circumstances.
If the two controllers share a substantial amount of operations, and the behavior of just one or two functions needs to be altered - why not! Maybe not the most elegant solution but hey, whatever works.
If the behavior of many or all controller operations is going to depend on '$scope.mode'...I'd say be careful. That seems like a dangerous path.
Angular services have always served me well when it comes to minimizing code replication between controllers. If there is a "good practice to minimizing repetitive code," I would say it would be services. They are global to your app and can be injected into multiple controllers without issue.
I hope that helps!

Resources