Uruchamianie kodu inicjalizacji AngularJS podczas ładowania widoku

93

Kiedy ładuję widok, chciałbym uruchomić kod inicjujący w skojarzonym z nim kontrolerze.

Aby to zrobić, użyłem dyrektywy ng-init w głównym elemencie mojego poglądu:

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

aw kontrolerze:

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

    $scope.isSaving = false;
}

Pierwsze pytanie: czy to właściwy sposób?

Następnie mam problem z kolejnością zachodzących wydarzeń. W widoku mam przycisk „zapisz”, który używa ng-disableddyrektywy jako takiej:

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

isClean()funkcja jest określona w sterowniku:

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

Jak widać, używa $scope.isSavingflagi, która została zainicjowana w init()funkcji.

PROBLEM: gdy widok jest załadowany, funkcja isClean nazywa się przed tym init()funkcji, stąd flaga isSavingjest undefined. Co mogę zrobić, aby temu zapobiec?

Sam
źródło

Odpowiedzi:

137

Kiedy twój widok się ładuje, to samo dzieje się z jego kontrolerem. Zamiast używać ng-init, po prostu wywołaj swoją init()metodę w kontrolerze:

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

Ponieważ kontroler działa wcześniej ng-init, rozwiązuje to również drugi problem.

Skrzypce


Jak John David Fivewspomniano, możesz nie chcieć dołączać tego do $scope, aby uczynić tę metodę prywatną.

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

Zobacz jsFiddle


Jeśli chcesz poczekać, aż pewne dane zostaną wstępnie ustawione, przenieś żądanie danych do rozwiązania lub dodaj obserwatora do tej kolekcji lub obiektu i wywołaj metodę init, gdy dane spełniają kryteria inicjalizacji. Zwykle usuwam obserwatora, gdy moje wymagania dotyczące danych są spełnione, więc funkcja init nie uruchamia się ponownie losowo, jeśli dane, które obserwujesz, zmieniają się i spełniają kryteria uruchomienia metody init.

var init = function () {
    // do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
                    if( newVal && newVal.length > 0) {
                        unwatch();
                        init();
                    }
                });
Mark Rajcok
źródło
8
A co, jeśli potrzebujesz danych z niektórych modeli do uruchomienia inicjalizacji? A może tylko niektóre dane dostępne na stronie podczas renderowania, aby inicjalizacja mogła działać?
Eugene
38
Funkcja init nie musi być dołączona do $ scope. Ustaw swoją funkcję init jako prywatną. Nigdy nie chcesz, aby funkcja init działała więcej niż raz, więc nie ujawniaj jej na $ scope.
John David Five,
2
Chciałbym uruchomić funkcję init za każdym razem, gdy wyświetlany jest mój widok, ale nie mam pojęcia, jak, funkcja jest uruchamiana tylko raz. Jakieś pomysły, jak mogę go uruchomić przy każdym ładowaniu strony / szablonu?
Jorre
9
Nie jestem ekspertem od kątowości, ale takie podejście jest do bani w testowaniu, ponieważ init () jest wywoływana po prostu przy tworzeniu instancji kontrolera ... innymi słowy, gdy trzeba przetestować jedną metodę kontrolera, wywoływana jest również funkcja init (). . przełamywanie testów!
Wagner Leonardi
1
Co powiedział @WagnerLeonardi. Takie podejście sprawia, że ​​testowanie "prywatnej" metody init () jest dość trudne.
Steven Rogers
36

Od AngularJS 1.5 powinniśmy używać tego,$onInit co jest dostępne w każdym komponencie AngularJS. Zaczerpnięte z dokumentacji cyklu życia komponentów od wersji 1.5 jest to preferowany sposób:

$ onInit () - wywoływana na każdym kontrolerze po skonstruowaniu wszystkich kontrolerów elementu i zainicjowaniu ich powiązań (a przed funkcjami łączenia przed i po dla dyrektyw tego elementu). Jest to dobre miejsce na umieszczenie kodu inicjalizacji kontrolera.

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


Zaawansowany przykład wykorzystania cyklu życia komponentów:

Cykl życia komponentu daje nam możliwość dobrego radzenia sobie z komponentami. Pozwala nam tworzyć zdarzenia np. „Init”, „change” lub „zniszcz” komponent. W ten sposób jesteśmy w stanie zarządzać rzeczami zależnymi od cyklu życia komponentu. Ten mały przykład pokazuje, jak zarejestrować i wyrejestrować $rootScopenasłuchiwanie zdarzeń $on. Wiedząc, że zdarzenie $onbinded na $rootScopenie będą undinded gdy sterownik traci swoje odniesienie w widoku lub uzyskiwania zniszczone musimy zniszczyć $rootScope.$onręcznie słuchacza. Dobrym miejscem na umieszczenie tych rzeczy jest $onDestroyfunkcja cyklu życia komponentu:

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

lin
źródło
17

Lub możesz po prostu zainicjować inline w kontrolerze. Jeśli używasz funkcji init wewnętrznej kontrolera, nie trzeba jej definiować w zakresie. W rzeczywistości może być wykonywany samodzielnie:

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
źródło
1
czy istnieje powód, dla którego kod init jest anonimowy?
Adam Tolley,
@AdamTolley nie ma konkretnego powodu. Po prostu definiuje funkcję i natychmiast ją wywołuje, bez wiązania jej z var.
Tair
7
Jak możesz w ten sposób przetestować jednostkowo prywatną funkcję init ()?
Steven Rogers
Tylko członkowie publiczni są testowani jednostkowo. Testy jednostkowe nie powinny być zależne od tego, co klasy robią prywatnie, aby uzyskać oczekiwane wyniki.
Phil
14

W swoich projektach używam następującego szablonu:

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
źródło
wygląda to na uporządkowane, ale iffe uniemożliwia uruchamianie metod, które definiujesz w zakresie, co często jest tym, co musimy zrobić, uruchomić je raz podczas uruchamiania, a następnie mieć je również w zakresie, aby móc je uruchomić ponownie w razie potrzeby użytkownika
chrismarx
to znaczy, jeśli jest uruchomiony na górze kontrolera
chrismarx