AngularJS – Controller code structure

Published by

on

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!

8 responses to “AngularJS – Controller code structure”

  1. Marco Avatar
    Marco

    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 🙂 )

    Liked by 1 person

  2. Juan Carlos Sánchez Avatar

    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)

    Like

    1. Marco Avatar
      Marco

      Thank you.
      Merry Christmas 😉

      Like

      1. Juan Carlos Sánchez Avatar

        Merry Christmas! 🙂

        Like

  3. Luis Pedraz (@PedrazL) Avatar

    Muy buen post Juan Carlos! Claro y muy útil, me encaja perfecto en la estructura que quiero para mis controladores. Gracias!

    Liked by 1 person

  4. elporfirio Avatar
    elporfirio

    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?

    Like

    1. Juan Carlos Sánchez Avatar

      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.

      Like

      1. elporfirio Avatar

        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.

        Like

Leave a reply to Juan Carlos Sánchez Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Blog at WordPress.com.