Is it good practice to combine CREATE and EDIT controllers in AngularJS? - 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!

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

Marionette Regions and routing

I'm using a LayoutView to display a collection in table form. When a user clicks on a tr I swap the CompositeView for an ItemView that shows the details using the same region. It all works except the functionality of the back button is broken. Is there a way to trap the back event and switch views?
Or should I use two Views and pass the model id and then refetch the model? The problem with that though is the extra request and I lose the filter and sort values of the table unless I use local storage.
Including more code would be better, but in any case I will try to give some guidance for your problem.
To avoid fetching the data twice, you can keep a common object in a "parent" component, for example in the Router.
var theObject;
var router = Marionette.AppRouter.extend({
routes: {
"routeA/:id": "goToRouteA",
"routeB/:id": "goToRouteB"
},
goToRouteA: function(id) {
MyRegion.show(new myLayout({
model: this._getCommonObject(id)
}));
},
goToRouteB: function(id) {
MyRegion.show(new myLayout({
model: this._getCommonObject(id)
}));
},
/*Return the common object for the views*/
_getCommonObject: function(id) {
theObject = (theObject && theObject.get('id') == id) ? theObject : MyApp.request('getTheObject');
return theObject;
}
});
In this way, you can keep the reference to the same object without loosing information.
You just have to make sure to delete the object when it is not needed to avoid keeping old information, for example on the Region close event.

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

Show layout in Marionette.Application inside controller

I am trying to structure my code with MVC flow within my application. I am trying to show created layouts in my marionette app instance within my marionette.controller as below..
Can anyone please tell me is it a proper way to show or change layouts within controller is proper way or not? And if not then what's the proper approach for that.
My Controller
define([ 'marionette', 'app', 'index_view' ], function( Marionette, App, IndexView ) {
console.log("Inside...ViewFlow Controller.");
var ViewFlow_Controller = Marionette.Controller.extend({
loadIndex : function() {
console.log("Inside...Load Index Method.");
App.main.show( new IndexView() );
}
});
return new ViewFlow_Controller();
});
where my IndexView is like this
define(['app', 'helper', 'templates'],
function (App, Helper, templates){
console.log("Inside...Index View.");
App.Page_Index = (function(){
var Page_Index = {};
var _pageName = 'IndexPage';
var _pageLayout = Helper.newPageLayout({
name:_pageName,
panelView: Helper.newPanelView(),
headerView: Helper.newHeaderView({name:_pageName, title:'Welcome to the Index Page'}),
contentView: Helper.newContentView({name:_pageName, template: templates.content_index}),
footerView: Helper.newFooterView({name:_pageName, title:'IndexPage Footer'})
});
return Page_Index;
})();
return App.Page_Index;
});
My helper returns me App_Layout instance.
But it's not working, it's giving me an error
Uncaught TypeError:object is not a function viewflow_controller.js:12
Please help me out.
You can find the code here if you want to refer to the complete code or contribute.
Thanks in advance.
The code on GitHub seems to contain only empty files (aside from the libraries), so I'm going to assume Helper returns a layout instance (which you seem to have indicated, saying it returned an App_Layout instance).
It looks like you're using layouts wrong. The way to use layouts is basically:
Create a layout instance with regions (e.g.) panelRegion and contentRegion
Create view instances that will be displayed in the layout (e.g.) panelViewInstance and contentViewInstance
Write a handler to show your views when the layout itself is shown.
The handler should look like this:
myLayout.on("show", function(){
myLayout.panelRegion.show(panelViewInstance);
myLayout.contentRegionshow(contentViewInstance);
});
Then, show that layout in one of your app's regions:
MyApp.mainRegion.show(myLayout);
The documentation on layouts is here: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.layout.md
You can learn more on using layouts and structuring your code in my book on Marionette.

Resources