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!
Great post! Thank you.
Why not a post about service code structure? (service life cycle, self variable and constructor function are obscure concepts for a OO programmer like me 🙂 )
LikeLiked by 1 person
Hi Marco, thank you for your comment!
I am also a OO programmer 🙂 but I will try to write something about angular services code structure (most probably after Christmas)
LikeLike
Thank you.
Merry Christmas 😉
LikeLike
Merry Christmas! 🙂
LikeLike
Muy buen post Juan Carlos! Claro y muy útil, me encaja perfecto en la estructura que quiero para mis controladores. Gracias!
LikeLiked by 1 person
Me gusto, se adapta muy bien a la estructura que tengo. Pero ¿como podria utiizar esto para llamar mis metodos publicos, desde una directiva?
El controllador es llamado desde una directiva. Normalmente se haria
$scope.method(params);
pero al cambiar al modo “self” se hace algo como:
UnBonitoController.method(params);
Sin embargo esto ultimo no funciona dentro de una directiva.. ¿que se puede hacer?
LikeLike
Hola gracias por tu comentario.
Creo que no entiendo bien tu pregunta, te refieres a hacer algo como:
https://github.com/softwarejc/angularjs-crudgrid/blob/master/Items.Web/app/items/items.controller.js
Normalmente utilizando dependency injection no tienes que acceder ningun otro controller mediante el scope.
LikeLike
jejejej ¿hace un año era tan malo en angular? demonios. En fin Llamar métodos desde una directiva pues bien.
La directiva tiene su propio controller así como su propia “link” function. El patrón que sigue angular, no permite llamarse de “Controlador a Controlador”, ni hacer dependencias de controlador a controlador.
Por lo que hay 2 pasos para la solución:
1. Crear un servicio, el cual se inyecta a los 2 controladores. ¿pero como decirle a un controlador que otro controlador hizo cambios?
2. Utilizar los “event emmiters” como $rootScope.$broadcast(‘cambie-datos’) Para que así, los controladores reaccionen al evento.
===== Definitivamente no usar:
$watch o watchers de angular, consumen mucho recursos y no son necesarios realmente.
LikeLike