Exécution du code d'initialisation AngularJS lorsque la vue est chargée

92

Lorsque je charge une vue, j'aimerais exécuter du code d'initialisation dans son contrôleur associé.

Pour ce faire, j'ai utilisé la directive ng-init sur l'élément principal de ma vue:

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

et dans le contrôleur:

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

    $scope.isSaving = false;
}

Première question: est-ce la bonne façon de procéder?

Ensuite, j'ai un problème avec la séquence des événements. Dans la vue, j'ai un bouton `` enregistrer '', qui utilise la ng-disableddirective en tant que telle:

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

la isClean()fonction est définie dans le contrôleur:

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

Comme vous pouvez le voir, il utilise l' $scope.isSavingindicateur, qui a été initialisé dans la init()fonction.

PROBLEME: lorsque la vue est chargé, la fonction isClean est appelée avant la init()fonction, d' où le drapeau isSavingest undefined. Que puis-je faire pour éviter cela?

Sam
la source

Réponses:

136

Lorsque votre vue se charge, son contrôleur associé fait de même. Au lieu d'utiliser ng-init, appelez simplement votre init()méthode dans votre contrôleur:

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

Étant donné que votre contrôleur s'exécute avant ng-init, cela résout également votre deuxième problème.

Violon


Comme John David Fivementionné, vous ne souhaiterez peut-être pas l'attacher $scopepour rendre cette méthode privée.

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

Voir jsFiddle


Si vous souhaitez attendre que certaines données soient prédéfinies, déplacez cette demande de données vers une résolution ou ajoutez un observateur à cette collection ou à cet objet et appelez votre méthode init lorsque vos données répondent à vos critères d'initialisation. Je supprime généralement l'observateur une fois que mes exigences en matière de données sont satisfaites, de sorte que la fonction init ne se réexécute pas au hasard si les données que vous regardez changent et répondent à vos critères pour exécuter votre méthode init.

var init = function () {
    // do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
                    if( newVal && newVal.length > 0) {
                        unwatch();
                        init();
                    }
                });
Mark Rajcok
la source
8
Que faire si vous avez besoin de données de certains modèles pour exécuter l'initialisation? Ou juste quelques données disponibles sur la page lors du rendu, pour que l'initialisation puisse fonctionner?
Eugene
38
Une fonction init n'a pas besoin d'être attachée à $ scope. Rendez votre fonction init privée. Vous ne voulez jamais qu'une fonction init s'exécute plus d'une fois, alors ne l'exposez pas sur $ scope.
John David Five
2
Je voudrais exécuter la fonction init à chaque fois que ma vue est affichée mais je n'ai aucune idée de comment, la fonction n'est exécutée qu'une seule fois. Des idées sur la façon dont je peux l'exécuter sur chaque chargement de page / modèle?
Jorre
9
Je ne suis pas un expert angulaire, mais cette approche est nulle pour les tests, car init () est appelé simplement à l'instanciation du contrôleur ... en d'autres termes, lorsque vous avez besoin de tester une seule méthode de contrôleur, init () est également appelée. . briser les tests!
Wagner Leonardi
1
Ce que @WagnerLeonardi a dit. Cette approche rend le test de votre méthode init () "privée" assez difficile.
Steven Rogers
36

Depuis AngularJS 1.5, nous devrions utiliser$onInit ce qui est disponible sur n'importe quel composant AngularJS. Tiré de la documentation sur le cycle de vie des composants depuis la v1.5, c'est la méthode préférée:

$ onInit () - Appelé sur chaque contrôleur une fois que tous les contrôleurs d'un élément ont été construits et leurs liaisons initialisées (et avant les fonctions de pré et post-liaison pour les directives sur cet élément). C'est un bon endroit pour mettre le code d'initialisation de votre contrôleur.

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

>> Démo Fiddle


Un exemple avancé d'utilisation du cycle de vie des composants:

Le cycle de vie des composants nous donne la possibilité de gérer correctement les composants. Cela nous permet de créer des événements pour par exemple "init", "change" ou "destroy" d'un composant. De cette façon, nous sommes en mesure de gérer des éléments qui dépendent du cycle de vie d'un composant. Ce petit exemple montre comment enregistrer et désinscrire un $rootScopeécouteur d'événement $on. En sachant, qu'un événement $onbinded sur $rootScopene sera pas undinded lorsque le contrôleur perd sa référence dans la vue ou se détruit , nous devons détruire un $rootScope.$onauditeur manuellement. Un bon endroit pour mettre ces choses est $onDestroyla fonction de cycle de vie d'un composant:

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

>> Démo Fiddle

lin
la source
17

Ou vous pouvez simplement initialiser en ligne dans le contrôleur. Si vous utilisez une fonction init interne au contrôleur, elle n'a pas besoin d'être définie dans la portée. En fait, il peut être auto-exécutable:

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
la source
1
y a-t-il une raison pour laquelle le code d'initialisation est dans une fermeture anonyme?
Adam Tolley
@AdamTolley il n'y a pas de raison particulière. Il définit simplement une fonction et l'appelle immédiatement, sans la lier à une variable.
Tair
7
Comment pouvez-vous tester correctement la fonction private init () de cette façon?
Steven Rogers le
Seuls les membres publics sont testés à l'unité. Les tests unitaires ne doivent pas dépendre de ce que les classes font en privé pour obtenir les résultats attendus.
Phil
14

J'utilise le modèle suivant dans mes projets:

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
la source
cela semble propre, mais l'iffe vous empêche d'exécuter les méthodes que vous définissez sur l'étendue, ce qui est souvent ce que nous devons faire, les exécuter une fois au démarrage, puis les avoir également sur l'étendue pour pouvoir les exécuter à nouveau selon les besoins de l'utilisateur
chrismarx
c'est-à-dire s'il est exécuté en haut du contrôleur
chrismarx