Execução do código de inicialização AngularJS quando a visualização é carregada

92

Ao carregar um modo de exibição, gostaria de executar algum código de inicialização em seu controlador associado.

Para fazer isso, usei a diretiva ng-init no elemento principal da minha visão:

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

e no controlador:

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

    $scope.isSaving = false;
}

Primeira pergunta: essa é a maneira certa de fazer isso?

Em seguida, tenho um problema com a sequência de eventos ocorrendo. Na visualização, tenho um botão 'salvar', que usa a ng-disableddiretiva como tal:

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

a isClean()função é definida no controlador:

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

Como você pode ver, ele usa o $scope.isSavingsinalizador, que foi inicializado na init()função.

PROBLEMA: quando a visualização é carregada, a função isClean é chamada antes da init()função, portanto, o sinalizador isSavingé undefined. O que posso fazer para evitar isso?

Sam
fonte

Respostas:

136

Quando sua visualização é carregada, o controlador associado também carrega. Em vez de usar ng-init, basta chamar seu init()método em seu controlador:

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

Como seu controlador é executado antes ng-init, isso também resolve seu segundo problema.

Violino


Conforme John David Fivemencionado, talvez você não queira anexar a $scopepara tornar esse método privado.

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

Veja jsFiddle


Se você quiser esperar que certos dados sejam predefinidos, mova essa solicitação de dados para uma resolução ou adicione um observador a essa coleção ou objeto e chame seu método init quando seus dados atenderem aos critérios de init. Eu geralmente removo o observador assim que meus requisitos de dados são atendidos para que a função init não seja executada novamente de forma aleatória se os dados que você está observando mudar e atender aos seus critérios para executar o 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
fonte
8
E se você precisar de dados de alguns modelos para executar a inicialização? Ou apenas alguns dados disponíveis na página durante a renderização, para que a inicialização pudesse funcionar?
Eugene,
38
Uma função init não precisa ser anexada a $ scope. Torne sua função init privada. Você nunca quer que uma função init rode mais de uma vez, então não a exponha em $ scope.
John David Five de
2
Eu gostaria de executar a função init sempre que meu modo de exibição for mostrado, mas não tenho ideia de como, a função é executada apenas uma vez. Alguma ideia de como posso executá-lo em cada carregamento de página / modelo?
Jorre
9
Não sou especialista em angular, mas essa abordagem é uma droga para o teste, já que init () é chamado simplesmente na instanciação do controlador ... em outras palavras, quando você precisa testar um único método de controlador, init () também é chamado. . quebrando os testes!
Wagner Leonardi
1
O que @WagnerLeonardi disse. Esta abordagem torna o teste de seu método init () "privado" bastante difícil.
Steven Rogers
36

Desde AngularJS 1.5, devemos usar o$onInit que está disponível em qualquer componente AngularJS. Retirado da documentação do ciclo de vida do componente desde a v1.5, é a forma preferida:

$ onInit () - Chamado em cada controlador depois que todos os controladores em um elemento foram construídos e tiveram suas ligações inicializadas (e antes das funções de pré e pós-vinculação para as diretivas neste elemento). Este é um bom lugar para colocar o código de inicialização do seu 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';
    }
});

>> Fiddle Demo


Um exemplo avançado de uso do ciclo de vida do componente:

O ciclo de vida do componente nos dá a capacidade de lidar com os componentes de uma boa maneira. Ele nos permite criar eventos para, por exemplo, "init", "change" ou "destroy" de um componente. Dessa forma, somos capazes de gerenciar coisas que dependem do ciclo de vida de um componente. Este pequeno exemplo mostra como registrar e cancelar o registro de um $rootScopeouvinte de evento $on. Sabendo que um evento $onligado $rootScopenão será desfeito quando o controlador perder sua referência na visão ou for destruído, precisamos destruir um $rootScope.$onouvinte manualmente. Um bom lugar para colocar essas coisas é a $onDestroyfunção de ciclo de vida de um 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();
  }
});

>> Demonstração de violino

lin
fonte
17

Ou você pode apenas inicializar em linha no controlador. Se você usar uma função init interna ao controlador, ela não precisa ser definida no escopo. Na verdade, pode ser autoexecutável:

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
fonte
1
há uma razão para que o código init esteja em um fechamento anônimo?
Adam Tolley
@AdamTolley não existe uma razão particular. Ele apenas define uma função e imediatamente a chama, sem vinculá-la a um var.
Tair
7
Como você pode fazer o teste de unidade da função init () privada adequadamente dessa maneira?
Steven Rogers
Somente membros públicos são testados na unidade. Os testes de unidade não devem depender do que as aulas fazem em particular para obter os resultados esperados.
Phil
14

Eu uso o seguinte modelo em meus projetos:

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
fonte
isso parece limpo, mas o iffe impede você de executar métodos que você está definindo no escopo, que geralmente é o que precisamos fazer, executá-los uma vez na inicialização e, em seguida, tê-los também no escopo para poder executá-los novamente conforme a necessidade do usuário
chrismarx
ou seja, se for executado na parte superior do controlador
chrismarx