AngularJS – Controller code structure

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!

Advertisements

8 thoughts on “AngularJS – Controller code structure

  1. 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. elporfirio says:

    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

      • 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

Your feedback is important...

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s