Запуск кода инициализации AngularJS при загрузке представления

93

Когда я загружаю представление, я хочу запустить некоторый код инициализации в связанном с ним контроллере.

Для этого я использовал директиву ng-init в основном элементе моего представления:

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

и в контроллере:

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

    $scope.isSaving = false;
}

Первый вопрос: правильно ли это делать?

Следующее, у меня проблема с последовательностью происходящих событий. В представлении у меня есть кнопка «сохранить», которая использует ng-disabledдирективу как таковую:

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

isClean()функция определена в контроллере:

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

Как видите, он использует $scope.isSavingфлаг, который был инициализирован в init()функции.

ПРОБЛЕМА: когда вид загружен, isClean функция вызывается перед тем в init()функции, следовательно , флаг isSavingявляется undefined. Что я могу сделать, чтобы это предотвратить?

Сэм
источник

Ответы:

137

Когда ваше представление загружается, то же самое делает и связанный с ним контроллер. Вместо использования ng-initпросто вызовите свой init()метод в контроллере:

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

Поскольку ваш контроллер работает раньше ng-init, это также решает вашу вторую проблему.

Скрипка


Как уже John David Fiveупоминалось, вы, возможно, не захотите прикреплять это к $scope, чтобы сделать этот метод закрытым.

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

См. JsFiddle


Если вы хотите дождаться предварительной настройки определенных данных, либо переместите этот запрос данных в разрешение, либо добавьте наблюдателя в эту коллекцию или объект и вызовите свой метод инициализации, когда ваши данные соответствуют вашим критериям инициализации. Я обычно удаляю наблюдателя, как только мои требования к данным удовлетворяются, чтобы функция init не запускалась случайным образом повторно, если наблюдаемые вами данные изменяются и соответствуют вашим критериям для запуска вашего метода инициализации.

var init = function () {
    // do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
                    if( newVal && newVal.length > 0) {
                        unwatch();
                        init();
                    }
                });
Марк Райкок
источник
8
Что делать, если вам нужны данные из некоторых моделей для запуска инициализации? Или просто некоторые данные, доступные на странице при рендеринге, чтобы инициализация могла работать?
Евгений
38
Функция инициализации не обязательно должна быть привязана к $ scope. Сделайте вашу функцию инициализации частной. Вы никогда не хотите, чтобы функция инициализации запускалась более одного раза, поэтому не выставляйте ее в $ scope.
Джон Дэвид Пять
2
Я хотел бы запускать функцию init каждый раз, когда отображается мое представление, но я понятия не имею, как, функция запускается только один раз. Есть идеи, как я могу запускать его при каждой загрузке страницы / шаблона?
Jorre
9
Я не эксперт по angular, но этот подход отстой для тестирования, поскольку init () вызывается просто при создании экземпляра контроллера ... другими словами, когда вам нужно протестировать один метод контроллера, также вызывается init (). .пройдя испытания!
Вагнер Леонарди
1
Что сказал @WagnerLeonardi. Такой подход затрудняет тестирование вашего «частного» метода init ().
Стивен Роджерс
36

Начиная с AngularJS 1.5, мы должны использовать то,$onInit что доступно в любом компоненте AngularJS. Взято из документации по жизненному циклу компонента, начиная с версии 1.5, это предпочтительный способ:

$ onInit () - вызывается на каждом контроллере после того, как все контроллеры элемента созданы и инициализированы их привязки (и перед функциями связывания до и после для директив этого элемента). Это хорошее место для размещения кода инициализации вашего контроллера.

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

>> Демо скрипки


Продвинутый пример использования жизненного цикла компонента:

Жизненный цикл компонента дает нам возможность правильно обрабатывать компоненты. Это позволяет нам создавать события, например, для «инициализации», «изменения» или «уничтожения» компонента. Таким образом, мы можем управлять вещами, которые зависят от жизненного цикла компонента. Этот небольшой пример показывает, как зарегистрировать и отменить регистрацию $rootScopeпрослушивателя событий $on. Зная, что событие $onпереплетено на $rootScopeне будет undinded , когда контроллер теряет свою ссылку в окне или получить уничтожено , мы должны уничтожить $rootScope.$onслушатель вручную. Хорошее место для этого - $onDestroyфункция жизненного цикла компонента:

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

>> Демо скрипки

Линь
источник
17

Или вы можете просто инициализировать встроенный в контроллер. Если вы используете внутреннюю для контроллера функцию инициализации, ее не нужно определять в области. Фактически, это может быть самовыполняющийся:

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 }
}
Джозеф Остер
источник
1
есть ли причина, по которой код инициализации находится в анонимном закрытии?
Адам Толли
@AdamTolley нет особой причины. Он просто определяет функцию и сразу вызывает ее, не привязывая к переменной.
Таир
7
Как таким образом можно правильно протестировать частную функцию init ()?
Стивен Роджерс,
Модульное тестирование проходят только публичные члены. Модульные тесты не должны зависеть от того, какие классы делают в частном порядке, чтобы получить ожидаемые результаты.
Фил
14

В своих проектах я использую следующий шаблон:

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
источник
это выглядит чистым, но iffe не позволяет вам запускать методы, которые вы определяете в области видимости, что часто является тем, что нам нужно сделать, запускать их один раз при запуске, а затем иметь их также в области, чтобы иметь возможность запускать их снова по мере необходимости пользователя
chrismarx
то есть, если он запущен в верхней части контроллер-
chrismarx