Chạy mã khởi tạo AngularJS khi chế độ xem được tải


92

Khi tôi tải một chế độ xem, tôi muốn chạy một số mã khởi tạo trong bộ điều khiển liên quan của nó.

Để làm như vậy, tôi đã sử dụng chỉ thị ng-init trên phần tử chính của chế độ xem của tôi:

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

và trong bộ điều khiển:

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

    $scope.isSaving = false;
}

Câu hỏi đầu tiên: đây có phải là cách làm đúng?

Điều tiếp theo, tôi gặp vấn đề với chuỗi sự kiện diễn ra. Trong dạng xem, tôi có một nút 'lưu', sử dụng lệnh ng-disablednhư sau:

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

các isClean()chức năng được xác định trong điều khiển:

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

Như bạn có thể thấy, nó sử dụng $scope.isSavingcờ, được khởi tạo trong init()hàm.

VẤN ĐỀ: khi xem được nạp, hàm isClean được gọi trước khi các init()chức năng, vì thế mà lá cờ isSavingundefined. Tôi có thể làm gì để ngăn chặn điều đó?

Câu trả lời:


136

Khi chế độ xem của bạn tải, bộ điều khiển liên quan của nó cũng vậy. Thay vì sử dụng ng-init, chỉ cần gọi init()phương thức của bạn trong bộ điều khiển của bạn:

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

Vì bộ điều khiển của bạn chạy trước đó ng-init, điều này cũng giải quyết vấn đề thứ hai của bạn.

Vĩ cầm


Như John David Fiveđã đề cập, bạn có thể không muốn đính kèm điều này $scopevào để đặt phương thức này ở chế độ riêng tư.

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

Xem jsFiddle


Nếu bạn muốn đợi dữ liệu nhất định được đặt trước, hãy di chuyển yêu cầu dữ liệu đó đến giải pháp hoặc thêm người theo dõi vào tập hợp hoặc đối tượng đó và gọi phương thức init của bạn khi dữ liệu của bạn đáp ứng tiêu chí init của bạn. Tôi thường xóa trình theo dõi khi các yêu cầu dữ liệu của tôi được đáp ứng, do đó, hàm init không ngẫu nhiên chạy lại nếu dữ liệu mà bạn đang xem thay đổi và đáp ứng tiêu chí của bạn để chạy phương thức init của bạn.

var init = function () {
    // do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
                    if( newVal && newVal.length > 0) {
                        unwatch();
                        init();
                    }
                });

8
Điều gì sẽ xảy ra nếu bạn cần dữ liệu từ một số mô hình để chạy khởi tạo? Hay chỉ một số dữ liệu có sẵn trên trang khi kết xuất, để quá trình khởi tạo có thể hoạt động?
Eugene

38
Một hàm init không cần phải được đính kèm với $ scope. Đặt chức năng init của bạn ở chế độ riêng tư. Bạn không bao giờ muốn một hàm init chạy nhiều lần vì vậy đừng để lộ nó trên $ scope.
John David Five

2
Tôi muốn chạy hàm init bất cứ khi nào chế độ xem của tôi được hiển thị nhưng tôi không biết làm thế nào, hàm chỉ được chạy một lần. Bất kỳ ý tưởng nào về cách tôi có thể chạy nó trên mỗi lần tải trang / mẫu?
Jorre

9
Tôi không phải là chuyên gia về góc cạnh, nhưng cách tiếp cận này rất tệ để kiểm tra, vì init () được gọi đơn giản là khởi tạo bộ điều khiển ... nói cách khác, khi bạn cần kiểm tra một phương thức duy nhất của bộ điều khiển, init () cũng được gọi. . đột phá các bài kiểm tra!
Wagner Leonardi

1
@WagnerLeonardi đã nói gì. Cách tiếp cận này làm cho việc kiểm tra phương thức init () "riêng tư" của bạn khá khó khăn.
Steven Rogers

36

Kể từ AngularJS 1.5, chúng ta nên sử dụng$onInit có sẵn trên bất kỳ thành phần AngularJS nào. Lấy từ tài liệu vòng đời thành phần kể từ phiên bản 1.5 theo cách được ưa chuộng:

$ onInit () - Được gọi trên mỗi bộ điều khiển sau khi tất cả các bộ điều khiển trên một phần tử đã được xây dựng và khởi tạo các ràng buộc của chúng (và trước các hàm liên kết trước & sau cho các chỉ thị trên phần tử này). Đây là một nơi tốt để đặt mã khởi tạo cho bộ điều khiển của bạn.

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

>> Bản trình diễn Fiddle


Một ví dụ nâng cao về việc sử dụng vòng đời thành phần:

Vòng đời của thành phần cho chúng ta khả năng xử lý nội dung thành phần theo cách tốt. Nó cho phép chúng ta tạo các sự kiện cho ví dụ: "init", "change" hoặc "kill" của một thành phần. Bằng cách đó, chúng tôi có thể quản lý nội dung phụ thuộc vào vòng đời của một thành phần. Ví dụ nhỏ này cho thấy để đăng ký và hủy đăng ký một $rootScopengười nghe sự kiện $on. Bởi biết rằng một sự kiện được liên kết $ontrên $rootScopesẽ không bị hủy bỏ khi bộ điều khiển mất tham chiếu của nó trong chế độ xem hoặc bị phá hủy, chúng ta cần phải hủy một trình $rootScope.$onnghe theo cách thủ công. Một nơi tốt để đặt nội dung đó là $onDestroychức năng vòng đời của một thành phần:

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

>> Bản demo Fiddle


17

Hoặc bạn chỉ có thể khởi tạo nội tuyến trong bộ điều khiển. Nếu bạn sử dụng một hàm init bên trong bộ điều khiển, nó không cần phải được xác định trong phạm vi. Trên thực tế, nó có thể tự thực thi:

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
có lý do gì khiến mã init ở trạng thái đóng ẩn danh không?
Adam Tolley

@AdamTolley không có lý do cụ thể nào. Anh ta chỉ định nghĩa một hàm và ngay lập tức gọi nó, mà không ràng buộc nó với một var.
Tair

7
Làm cách nào để đơn vị có thể kiểm tra hàm private init () theo cách này?
Steven Rogers

Chỉ các thành viên công cộng mới được kiểm tra đơn vị. Bài kiểm tra đơn vị không nên phụ thuộc vào những gì các lớp thực hiện riêng để có được kết quả mong đợi.
Phil

14

Tôi sử dụng mẫu sau trong các dự án của mình:

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  */ {
            // ...
        }

    });

điều này trông có vẻ sạch sẽ, nhưng iffe ngăn bạn chạy các phương pháp mà bạn đang xác định trên phạm vi, đây thường là những gì chúng tôi cần làm, chạy chúng một lần khi khởi động, và sau đó đặt chúng cũng trên phạm vi để có thể chạy chúng một lần nữa khi cần thiết bởi người sử dụng
chrismarx

có nghĩa là, nếu nó chạy ở phía trên cùng của controller-
chrismarx
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.