95

When I load a view, I'd like to run some initialization code in its associated controller.

To do so, I've used the ng-init directive on the main element of my view:

<div ng-init="init()">
  blah
</div>

and in the controller:

$scope.init = function () {
    if ($routeParams.Id) {
        //get an existing object
        });
    } else {
       //create a new object
    }

    $scope.isSaving = false;
}

First question: is this the right way to do it?

Next thing, I have a problem with the sequence of events taking place. In the view I have a 'save' button, which uses the ng-disabled directive as such:

<button ng-click="save()" ng-disabled="isClean()">Save</button>

the isClean() function is defined in the controller:

$scope.isClean = function () {
    return $scope.hasChanges() && !$scope.isSaving;
}

As you can see, it uses the $scope.isSaving flag, which was initialized in the init() function.

PROBLEM: when the view is loaded, the isClean function is called before the init() function, hence the flag isSaving is undefined. What can I do to prevent that?

Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
Sam
  • 13,934
  • 26
  • 108
  • 194

4 Answers4

139

When your view loads, so does its associated controller. Instead of using ng-init, simply call your init() method in your controller:

$scope.init = function () {
    if ($routeParams.Id) {
        //get an existing object
    } else {
        //create a new object
    }
    $scope.isSaving = false;
}
...
$scope.init();

Since your controller runs before ng-init, this also solves your second issue.

Fiddle


As John David Five mentioned, you might not want to attach this to $scope in order to make this method private.

var init = function () {
    // do something
}
...
init();

See jsFiddle


If you want to wait for certain data to be preset, either move that data request to a resolve or add a watcher to that collection or object and call your init method when your data meets your init criteria. I usually remove the watcher once my data requirements are met so the init function doesnt randomly re-run if the data your watching changes and meets your criteria to run your init method.

var init = function () {
    // do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
                    if( newVal && newVal.length > 0) {
                        unwatch();
                        init();
                    }
                });
Darrion H
  • 3
  • 2
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • cool, I used to do it like that but I thought that was bad practice to do it this way. thanks ! – Sam Apr 22 '13 at 18:07
  • 8
    What if you need data from some models to run initialization? Or just some data available on the page when rendering, so that initialization could work? – Eugene Apr 01 '14 at 09:29
  • 39
    An init function doesn't need to be attached to $scope. Make your init function private. You never want an init function to run more than once so don't expose it on $scope. – John David Five Oct 18 '14 at 18:35
  • 2
    I would like to run the init function any time my view is shown but I have no clue how, the function is only run once. Any ideas how I can run it on every page/template load? – Jorre Nov 20 '14 at 16:28
  • i'm looking for the same thing as @Jorre – Saad Masood Mar 19 '15 at 07:48
  • 9
    I'm not angular expert, but this approach sucks for testing, since init() is called at simply controller instantiation...in other words, when you need to test a single method of controller, init() is called as well ..breaking the tests! – Wagner Leonardi Oct 27 '15 at 20:43
  • 1
    What @WagnerLeonardi said. This approach makes testing your "private" init() method quite difficult. – Steven Rogers Apr 04 '16 at 16:17
  • Did anyone figure out a way to test a private init method? – jbustamovej May 11 '17 at 06:34
  • I would suggest using an iffe. `(function onInit(){ // Do something })(); ` – janekkkk Jan 07 '20 at 07:47
  • Let's assume that I want to run the function every time a view is shown, say `runEveryTimeOnViewOpen()` function, how can I do that? – Asad Shakeel Aug 17 '20 at 05:46
40

Since AngularJS 1.5 we should use $onInit which is available on any AngularJS component. Taken from the component lifecycle documentation since v1.5 its the preferred way:

$onInit() - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element). This is a good place to put initialization code for your controller.

var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function ($scope) {

    //default state
    $scope.name = '';

    //all your init controller goodness in here
    this.$onInit = function () {
      $scope.name = 'Superhero';
    }
});

Fiddle Demo

An advanced example of using component lifecycle:

The component lifecycle gives us the ability to handle component stuff in a good way. It allows us to create events for e.g. "init", "change" or "destroy" of an component. In that way we are able to manage stuff which is depending on the lifecycle of an component. This little example shows to register & unregister an $rootScope event listener $on. By knowing, that an event $on bound on $rootScope will not be unbound when the controller loses its reference in the view or getting destroyed we need to destroy a $rootScope.$on listener manually.

A good place to put that stuff is $onDestroy lifecycle function of an component:

var myApp = angular.module('myApp',[]);

myApp.controller('MyCtrl', function ($scope, $rootScope) {

  var registerScope = null;

  this.$onInit = function () {
    //register rootScope event
    registerScope = $rootScope.$on('someEvent', function(event) {
        console.log("fired");
    });
  }

  this.$onDestroy = function () {
    //unregister rootScope event by calling the return function
    registerScope();
  }
});

Fiddle demo

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
lin
  • 17,956
  • 4
  • 59
  • 83
17

Or you can just initialize inline in the controller. If you use an init function internal to the controller, it doesn't need to be defined in the scope. In fact, it can be self executing:

function MyCtrl($scope) {
    $scope.isSaving = false;

    (function() {  // init
        if (true) { // $routeParams.Id) {
            //get an existing object
        } else {
            //create a new object
        }
    })()

    $scope.isClean = function () {
       return $scope.hasChanges() && !$scope.isSaving;
    }

    $scope.hasChanges = function() { return false }
}
Joseph Oster
  • 5,507
  • 1
  • 20
  • 11
14

I use the following template in my projects:

angular.module("AppName.moduleName", [])

/**
 * @ngdoc controller
 * @name  AppName.moduleName:ControllerNameController
 * @description Describe what the controller is responsible for.
 **/
    .controller("ControllerNameController", function (dependencies) {

        /* type */ $scope.modelName = null;
        /* type */ $scope.modelName.modelProperty1 = null;
        /* type */ $scope.modelName.modelPropertyX = null;

        /* type */ var privateVariable1 = null;
        /* type */ var privateVariableX = null;

        (function init() {
            // load data, init scope, etc.
        })();

        $scope.modelName.publicFunction1 = function () /* -> type  */ {
            // ...
        };

        $scope.modelName.publicFunctionX = function () /* -> type  */ {
            // ...
        };

        function privateFunction1() /* -> type  */ {
            // ...
        }

        function privateFunctionX() /* -> type  */ {
            // ...
        }

    });
schirrmacher
  • 2,341
  • 2
  • 27
  • 29
  • this looks clean, but the iffe precludes you from running methods that you're defining on the scope, which is often what we need to do, run them once at startup, and then have them also on the scope to be able to run them again as needed by the user – chrismarx Dec 23 '15 at 20:03
  • that is, if it's run at the top of the controller- – chrismarx Dec 23 '15 at 20:17