The goal of this post is to define a structure to write AngularJS controllers.
I identify three different parts inside the controller:
- Public methods and variables: On top of the code and just the definition of the methods, the implementation will be inside the private area.
- Code that run when the controller is created. Right after the public methods.
- Private methods and variables. Prefixed with ‘_’.
This is the structure schema of my controllers:
app.controller("myController", myController); function myController() { 'use strict'; var self = this; //// ---------------- PUBLIC ---------------- //// PUBLIC fields ... //// PUBLIC Methods ... //// ---------------- CODE TO RUN ----------- ... //// ---------------- PRIVATE --------------- //// PRIVATE fields ... //// PRIVATE Functions - Public Methods Implementation ... //// PRIVATE Functions ... };
This is one of my controllers, a lot of code but someone interested only in its use and not in its implementation can easily read the ‘public’ area only :
stockModule.controller("crudgridController", itemsController); function itemsController($element, $attrs, ajaxServiceFactory, notificationsFactory, modalWindowFactory) { 'use strict'; var self = this; //// ---------------- PUBLIC ----------------- //// PUBLIC fields // all items self.allItems = []; // the item being added self.newItem = { name: '' }; // indicates if the view is being loaded self.loading = false; // indicates if the view is in add mode self.addMode = false; //// PUBLIC Methods // Initialize module self.initialize = _initialize; // Toggle the grid between add and normal mode self.toggleAddMode = _toggleAddMode; // Toggle an item between normal and edit mode self.toggleEditMode = _toggleEditMode; // Creates the 'newItem' on the server self.createItem = _createItem; // Gets an item from the server using the id self.readItem = _readItem; // Updates an item self.updateItem = _updateItem; // Deletes an item self.deleteItem = _deleteItem; // Get all items from the server self.getAllItems = _getAllItems; // In edit mode, if user press ENTER, update item self.updateOnEnter = _updateOnEnter; // In add mode, if user press ENTER, add item self.createOnEnter = _createOnEnter; //// ---------------- CODE TO RUN ------------ self.initialize(); //// ---------------- PRIVATE ---------------- //// PRIVATE fields var _itemsService; //// PRIVATE Functions - Public Methods Implementation function _initialize() { // create a service to do the communication with the server _itemsService = ajaxServiceFactory.getService($attrs.endPoint); // Initialize self.getAllItems(); } function _toggleAddMode() { self.addMode = !self.addMode; // Default new item name is empty self.newItem.name = ''; }; function _toggleEditMode(item) { // Toggle item.editMode = !item.editMode; // if item is not in edit mode anymore if (!item.editMode) { // Restore name item.name = item.serverName; } else { // save server name to restore it if the user cancel edition item.serverName = item.name; // Set edit mode = false and restore the name for the rest of items in edit mode // (there should be only one) self.allItems.forEach(function (i) { // item is not the item being edited now and it is in edit mode if (item.id != i.id && i.editMode) { // Restore name i.name = i.serverName; i.editMode = false; } }); } }; function _createItem(item) { // Check if the item is already on the list var duplicated = isNameDuplicated(item.name); if (!duplicated) { _itemsService.save(item, // success response function (createdItem) { // Add at the first position self.allItems.unshift(createdItem); self.toggleAddMode(); requestSuccess(); }, requestError); } else { notificationsFactory.error("The item already exists."); } }; function _readItem(itemId) { _itemsService.get({ id: itemId }, requestSuccess, requestError); }; function _updateItem(item) { item.editMode = false; // Only update if there are changes if (isDirty(item)) { _itemsService.update({ id: item.id }, item, function () { requestSuccess(); }, requestError); } }; function _deleteItem(item) { var title = "Delete '" + item.name + "'"; var msg = "Are you sure you want to remove this item?"; var confirmCallback = function () { return _itemsService.delete( // id { id: item.id }, // item item, // success callback function () { requestSuccess(); // Remove from scope var index = self.allItems.indexOf(item); self.allItems.splice(index, 1); }, // error callback requestError); }; modalWindowFactory.show(title, msg, confirmCallback); }; function _getAllItems() { self.loading = true; self.allItems = _itemsService.query(function () { self.loading = false; }, requestError); }; function _updateOnEnter(item, args) { // if key is enter if (args.keyCode == 13) { self.updateItem(item); // remove focus args.target.blur(); } }; function _createOnEnter(args, item) { // if key is enter if (args.keyCode == 13) { self.createItem(item); // remove focus args.target.blur(); } }; //// PRIVATE Functions function requestSuccess() { notificationsFactory.success(); }; function requestError() { notificationsFactory.error(); }; function isNameDuplicated(itemName) { return self.allItems.some(function (entry) { return entry.name.toUpperCase() == itemName.toUpperCase(); }); }; function isDirty(item) { return item.name != item.serverName; }; };
Controller as Syntax
I recommend to use the “controller as” syntax. There are some advantages using this syntax for example using it we can do:
this.myMethod…
instead of:
$scope.myMethod
And also we can add the controller when I create binds in HTML:
{{mycontroller.myMethod}}
instead of:
{{myMethod}}
But if we use ‘this’ instead of ‘$scope’, how can we reference the properties and methods of the controller inside a function?
To get the scope of the controller inside a function. I define this on top of my controller:
var self = this;
The rest of my code always use “self” to reference the scope of the controller, this also works inside functions.
Conclusion
This structure helps to write more readable code. Leave me a comment with the structure you use!
Leave a reply to Juan Carlos Sánchez Cancel reply