Ejecutando el código de inicialización de AngularJS cuando se carga la vista

92

Cuando cargo una vista, me gustaría ejecutar algún código de inicialización en su controlador asociado.

Para hacerlo, he usado la directiva ng-init en el elemento principal de mi vista:

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

y en el controlador:

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

    $scope.isSaving = false;
}

Primera pregunta: ¿es esta la forma correcta de hacerlo?

A continuación, tengo un problema con la secuencia de eventos que tienen lugar. En la vista tengo un botón 'guardar', que usa la ng-disableddirectiva como tal:

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

la isClean()función está definida en el controlador:

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

Como puede ver, usa la $scope.isSavingbandera, que se inicializó en la init()función.

PROBLEMA: cuando se carga la vista, se llama a la función isClean antes que a la init()función, por lo que la bandera isSavinges undefined. ¿Qué puedo hacer para evitarlo?

Sam
fuente

Respuestas:

136

Cuando su vista se carga, también lo hace su controlador asociado. En lugar de usar ng-init, simplemente llame a su init()método en su controlador:

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

Dado que su controlador se ejecuta antes ng-init, esto también resuelve su segundo problema.

Violín


Como se John David Fivemencionó, es posible que no desee adjuntar esto $scopepara que este método sea privado.

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

Ver jsFiddle


Si desea esperar a que ciertos datos estén predeterminados, mueva esa solicitud de datos a una resolución o agregue un observador a esa colección u objeto y llame a su método init cuando sus datos cumplan con sus criterios de inicio. Por lo general, elimino el observador una vez que se cumplen mis requisitos de datos, por lo que la función init no se vuelve a ejecutar aleatoriamente si los datos que está viendo cambian y cumplen con sus criterios para ejecutar su método init.

var init = function () {
    // do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
                    if( newVal && newVal.length > 0) {
                        unwatch();
                        init();
                    }
                });
Mark Rajcok
fuente
8
¿Qué sucede si necesita datos de algunos modelos para ejecutar la inicialización? ¿O solo algunos datos disponibles en la página al renderizar, para que la inicialización funcione?
Eugene
38
No es necesario adjuntar una función de inicio a $ scope. Haga que su función init sea privada. Nunca querrás que una función init se ejecute más de una vez, así que no la expongas en $ scope.
John David Five
2
Me gustaría ejecutar la función init cada vez que se muestre mi vista, pero no tengo ni idea de cómo, la función solo se ejecuta una vez. ¿Alguna idea de cómo puedo ejecutarlo en cada carga de página / plantilla?
Jorre
9
No soy un experto angular, pero este enfoque apesta para las pruebas, ya que init () se llama simplemente en la instanciación del controlador ... en otras palabras, cuando necesita probar un solo método de controlador, también se llama a init (). .rompiendo las pruebas!
Wagner Leonardi
1
Lo que dijo @WagnerLeonardi. Este enfoque hace que probar su método init () "privado" sea bastante difícil.
Steven Rogers
36

Desde AngularJS 1.5 deberíamos usar el$onInit que esté disponible en cualquier componente de AngularJS. Tomado de la documentación del ciclo de vida del componente desde v1.5 es la forma preferida:

$ onInit (): se llama en cada controlador después de que todos los controladores de un elemento se han construido y se han inicializado sus enlaces (y antes de las funciones de enlace pre y post para las directivas de este elemento). Este es un buen lugar para colocar el código de inicialización de su controlador.

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';
    }
});

>> Demostración de violín


Un ejemplo avanzado del uso del ciclo de vida de los componentes:

El ciclo de vida de los componentes nos da la capacidad de manejar las cosas de los componentes de una buena manera. Nos permite crear eventos para, por ejemplo, "init", "cambiar" o "destruir" un componente. De esa manera, podemos administrar cosas que dependen del ciclo de vida de un componente. Este pequeño ejemplo muestra cómo registrar y anular el registro de un $rootScopedetector de eventos $on. Al saber, que un evento $onbinded en $rootScopeno será undinded cuando el controlador pierde su referencia en la vista o ser destruido tenemos que destruir un $rootScope.$onoyente de forma manual. Un buen lugar para poner esas cosas es $onDestroyla función de ciclo de vida de un componente:

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();
  }
});

>> Demostración de violín

lin
fuente
17

O simplemente puede inicializar en línea en el controlador. Si utiliza una función de inicio interna al controlador, no es necesario definirla en el alcance. De hecho, puede ser autoejecutable:

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
fuente
1
¿Hay alguna razón por la que el código de inicio esté en un cierre anónimo?
Adam Tolley
@AdamTolley no hay ninguna razón en particular. Simplemente define una función y la llama inmediatamente, sin vincularla a una var.
Tair
7
¿Cómo puede realizar una prueba unitaria de la función privada init () correctamente de esta manera?
Steven Rogers
Solo los miembros públicos son probados por unidad. Las pruebas unitarias no deberían depender de lo que hagan las clases en privado para obtener los resultados esperados.
Phil
14

Utilizo la siguiente plantilla en mis proyectos:

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
fuente
esto parece limpio, pero el iffe le impide ejecutar métodos que está definiendo en el alcance, que a menudo es lo que debemos hacer, ejecutarlos una vez al inicio y luego tenerlos también en el alcance para poder ejecutarlos de nuevo según lo necesite el usuario
chrismarx
es decir, si se ejecuta en la parte superior del controlador
chrismarx