AngularJS: Làm thế nào để chạy mã bổ sung sau khi AngularJS đã kết xuất một mẫu?


182

Tôi có một mẫu Angular trong DOM. Khi bộ điều khiển của tôi nhận được dữ liệu mới từ một dịch vụ, nó sẽ cập nhật mô hình trong phạm vi $ và hiển thị lại mẫu. Tất cả đều tốt cho đến nay.

Vấn đề là tôi cũng cần phải thực hiện một số công việc bổ sung sau khi mẫu được kết xuất lại và nằm trong DOM (trong trường hợp này là một plugin jQuery).

Có vẻ như nên có một sự kiện để lắng nghe, chẳng hạn như AfterRender, nhưng tôi không thể tìm thấy bất kỳ điều gì như vậy. Có lẽ một chỉ thị sẽ là một cách để đi, nhưng nó dường như cũng bắn quá sớm.

Đây là một jsFiddle phác thảo vấn đề của tôi: Fiddle-AngularIssue

== CẬP NHẬT ==

Dựa trên các nhận xét hữu ích, theo đó, tôi đã chuyển sang chỉ thị để xử lý thao tác DOM và triển khai mô hình $ watch bên trong chỉ thị. Tuy nhiên, tôi vẫn có vấn đề cơ bản tương tự; mã bên trong sự kiện $ watch kích hoạt trước khi mẫu được biên dịch và chèn vào DOM, do đó, plugin jquery luôn đánh giá một bảng trống.

Thật thú vị, nếu tôi loại bỏ cuộc gọi không đồng bộ, toàn bộ hoạt động tốt, vì vậy đó là một bước đi đúng hướng.

Dưới đây là Fiddle cập nhật của tôi để phản ánh những thay đổi này: http://jsfiddle.net/uNREn/12/


Điều này có vẻ tương tự như một câu hỏi tôi đã có. stackoverflow.com/questions/11444494/ . Có lẽ một cái gì đó trong đó có thể giúp đỡ.
dnc253

Câu trả lời:


39

Bài đăng này đã cũ, nhưng tôi thay đổi mã của bạn thành:

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

xem jsfiddle .

Tôi hy vọng nó sẽ giúp bạn


Cảm ơn, điều đó rất hữu ích. Đây là giải pháp tốt nhất cho tôi vì nó không yêu cầu thao tác dom từ bộ điều khiển và vẫn sẽ hoạt động khi dữ liệu được cập nhật từ phản hồi dịch vụ không đồng bộ thực sự (và tôi không phải sử dụng $ evalAsync trong bộ điều khiển của mình).
gorebash

9
Điều này đã được phản đối? Khi tôi thử nó, nó thực sự gọi ngay trước khi DOM được hiển thị. Là nó phụ thuộc vào một dom bóng hoặc cái gì đó?
Shayne

Tôi còn khá mới đối với Angular và tôi không thể thấy cách thực hiện câu trả lời này với trường hợp cụ thể của mình. Tôi đã xem xét nhiều câu trả lời và đoán, nhưng điều đó không hiệu quả. Tôi không chắc chức năng 'xem' nên xem là gì. Ứng dụng góc cạnh của chúng tôi có nhiều hướng dẫn khác nhau, sẽ thay đổi từ trang này sang trang khác, vậy làm thế nào để tôi biết nên xem gì? Chắc chắn có một cách đơn giản để loại bỏ một số mã khi một trang đã được hoàn thành?
moosefetcher 8/12/2016

63

Đầu tiên, đúng nơi để gây rối với kết xuất là các chỉ thị. Lời khuyên của tôi sẽ là bọc DOM thao tác các plugin jQuery bằng các lệnh như thế này.

Tôi đã có cùng một vấn đề và đã đưa ra đoạn trích này. Nó sử dụng $watch$evalAsyncđể đảm bảo mã của bạn chạy sau khi các lệnh như ng-repeatđã được giải quyết và các mẫu như {{ value }}đã được hiển thị.

app.directive('name', function() {
    return {
        link: function($scope, element, attrs) {
            // Trigger when number of children changes,
            // including by directives like ng-repeat
            var watch = $scope.$watch(function() {
                return element.children().length;
            }, function() {
                // Wait for templates to render
                $scope.$evalAsync(function() {
                    // Finally, directives are evaluated
                    // and templates are renderer here
                    var children = element.children();
                    console.log(children);
                });
            });
        },
    };
});

Hy vọng điều này có thể giúp bạn ngăn chặn một số cuộc đấu tranh.


Điều này có thể nói rõ, nhưng nếu điều này không phù hợp với bạn, hãy kiểm tra các hoạt động không đồng bộ trong các chức năng / bộ điều khiển liên kết của bạn. Tôi không nghĩ $ evalAsync có thể tính đến những điều đó.
LOAS

Đáng lưu ý rằng bạn cũng có thể kết hợp một linkchức năng với a templateUrl.
Wtower

35

Theo lời khuyên của Misko, nếu bạn muốn hoạt động không đồng bộ, thì thay vì $ timeout () (không hoạt động)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

sử dụng $ evalAsync () (không hoạt động)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

Fiddle . Tôi cũng đã thêm một liên kết "xóa hàng dữ liệu" sẽ sửa đổi $ scope.assignments, mô phỏng thay đổi dữ liệu / mô hình - để cho thấy việc thay đổi dữ liệu hoạt động.

Phần Thời gian chạy của trang Tổng quan về Khái niệm giải thích rằng evalAsync nên được sử dụng khi bạn cần một cái gì đó xảy ra bên ngoài khung ngăn xếp hiện tại, nhưng trước khi trình duyệt hiển thị. (Đoán ở đây ... "khung ngăn xếp hiện tại" có thể bao gồm các cập nhật DOM Angular.) Sử dụng $ timeout nếu bạn cần một cái gì đó xảy ra sau khi trình duyệt hiển thị.

Tuy nhiên, như bạn đã tìm ra, tôi không nghĩ rằng có bất kỳ nhu cầu nào cho hoạt động không đồng bộ ở đây.


4
$scope.$evalAsync($scope.assignmentsLoaded(data));IMO không có ý nghĩa gì. Đối số cho $evalAsync()nên là một hàm. Trong ví dụ của bạn, bạn đang gọi hàm tại thời điểm một hàm nên được đăng ký để thực hiện sau này.
hgoebl

@hgoebl, đối số cho $ evalAsync có thể là hàm hoặc biểu thức. Trong trường hợp này tôi đã sử dụng một biểu thức. Nhưng bạn đã đúng, biểu thức được thực thi ngay lập tức. Tôi đã sửa đổi câu trả lời để bọc nó trong một hàm để thực hiện sau này. Cảm ơn đã nắm bắt điều đó.
Mark Rajcok

26

Tôi đã tìm thấy giải pháp đơn giản (rẻ tiền và vui vẻ) chỉ đơn giản là thêm một khoảng trống với ng-show = "someFunctionThatAlwaysReturnsZeroOrNoth ()" vào cuối phần tử cuối cùng được hiển thị. Hàm này sẽ được chạy khi kiểm tra xem phần tử span có được hiển thị không. Thực thi bất kỳ mã nào khác trong chức năng này.

Tôi nhận ra đây không phải là cách thanh lịch nhất để làm mọi thứ, tuy nhiên, nó hiệu quả với tôi ...

Tôi đã gặp một tình huống tương tự, mặc dù hơi đảo ngược khi tôi cần gỡ bỏ chỉ báo tải khi hoạt ảnh bắt đầu, trên các thiết bị di động góc cạnh đang khởi tạo nhanh hơn nhiều so với hoạt hình được hiển thị và sử dụng ng-cloak là không đủ vì chỉ báo tải loại bỏ tốt trước khi bất kỳ dữ liệu thực sự được hiển thị. Trong trường hợp này, tôi chỉ thêm hàm return 0 của mình vào phần tử được kết xuất đầu tiên và trong hàm đó đã lật var ẩn ẩn chỉ báo tải. (tất nhiên tôi đã thêm một ng-ẩn vào chỉ báo tải được kích hoạt bởi chức năng này.


3
Đây là một hack chắc chắn nhưng nó chính xác là những gì tôi cần. Buộc mã của tôi chạy chính xác sau khi kết xuất (hoặc rất gần với chính xác). Trong một thế giới lý tưởng tôi sẽ không làm điều này nhưng ở đây chúng ta ...
Andrew

Tôi cần $anchorScrolldịch vụ tích hợp để được gọi sau khi DOM hiển thị và dường như không có gì để thực hiện thủ thuật này. Hackish nhưng hoạt động.
Pat Migliaccio

ng-init là một lựa chọn tốt hơn
Flying Gambit

1
ng-init có thể không luôn luôn hoạt động. Ví dụ, tôi có một lặp lại ng thêm một số hình ảnh vào dom. Nếu tôi muốn gọi một hàm sau mỗi hình ảnh được thêm vào trong lặp lại thì tôi phải sử dụng ng-show.
Michael Bremerkamp


4

Cuối cùng tôi tìm thấy giải pháp, tôi đang sử dụng dịch vụ REST để cập nhật bộ sưu tập của mình. Để chuyển đổi jquery có thể truy cập được là mã sau đây:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });

3

tôi đã phải làm điều này khá thường xuyên. Tôi có một chỉ thị và cần phải thực hiện một số công cụ jquery sau khi công cụ mô hình được tải đầy đủ vào DOM. vì vậy tôi đặt logic của mình vào liên kết: hàm của lệnh và bọc mã trong setTimeout (function () {.....}, 1); setTimout sẽ kích hoạt sau khi DOM được tải và 1 triệu giây là khoảng thời gian ngắn nhất sau khi DOM được tải trước khi mã thực thi. điều này có vẻ hiệu quả với tôi nhưng tôi ước rằng angular đã đưa ra một sự kiện khi một mẫu được tải xong để các lệnh được sử dụng bởi mẫu đó có thể thực hiện các công cụ jquery và truy cập các phần tử DOM. hi vọng điêu nay co ich.



2

Cả $ scope. $ EvalAsync () hoặc $ timeout (fn, 0) đều hoạt động đáng tin cậy đối với tôi.

Tôi đã phải kết hợp cả hai. Tôi đã thực hiện một chỉ thị và cũng đặt mức độ ưu tiên cao hơn giá trị mặc định cho biện pháp tốt. Đây là một chỉ thị cho nó (Lưu ý tôi sử dụng ngInject để tiêm phụ thuộc):

app.directive('postrenderAction', postrenderAction);

/* @ngInject */
function postrenderAction($timeout) {
    // ### Directive Interface
    // Defines base properties for the directive.
    var directive = {
        restrict: 'A',
        priority: 101,
        link: link
    };
    return directive;

    // ### Link Function
    // Provides functionality for the directive during the DOM building/data binding stage.
    function link(scope, element, attrs) {
        $timeout(function() {
            scope.$evalAsync(attrs.postrenderAction);
        }, 0);
    }
}

Để gọi lệnh, bạn sẽ làm điều này:

<div postrender-action="functionToRun()"></div>

Nếu bạn muốn gọi nó sau khi chạy lặp lại ng, tôi đã thêm một khoảng trống trong ng-repeat và ng-if = "$ last":

<li ng-repeat="item in list">
    <!-- Do stuff with list -->
    ...

    <!-- Fire function after the last element is rendered -->
    <span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>

1

Trong một số trường hợp bạn cập nhật dịch vụ và chuyển hướng đến chế độ xem mới (trang) và sau đó lệnh của bạn được tải trước khi dịch vụ của bạn được cập nhật, bạn có thể sử dụng $ rootScope. $ Broadcast nếu $ watch hoặc $ timeout không thành công

Lượt xem

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

Bộ điều khiển

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

Chỉ thị

app.directive("serviceHistory", function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
           log: '='
        },
        link: function($scope, element, attrs) {
            function updateHistory() {
               if(log) {
                   //do something
               }
            }
            $rootScope.$on("history-updation", updateHistory);
        }
   };
});

1

Tôi đến với một giải pháp khá đơn giản. Tôi không chắc liệu đó có phải là cách chính xác để làm hay không nhưng nó hoạt động theo nghĩa thực tế. Hãy trực tiếp xem những gì chúng ta muốn được kết xuất. Ví dụ: trong một lệnh bao gồm một số ng-repeats, tôi sẽ coi chừng độ dài của văn bản (bạn có thể có những thứ khác!) Của đoạn văn hoặc toàn bộ html. Lệnh sẽ như thế này:

.directive('myDirective', [function () {
    'use strict';
    return {

        link: function (scope, element, attrs) {
            scope.$watch(function(){
               var whole_p_length = 0;
               var ps = element.find('p');
                for (var i=0;i<ps.length;i++){
                    if (ps[i].innerHTML == undefined){
                        continue
                    }
                    whole_p_length+= ps[i].innerHTML.length;
                }
                //it could be this too:  whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
                console.log(whole_p_length);
                return whole_p_length;
            }, function (value) {   
                //Code you want to be run after rendering changes
            });
        }
}]);

LƯU Ý rằng mã thực sự chạy sau khi kết xuất thay đổi hoàn thành kết xuất. Nhưng tôi đoán trong hầu hết các trường hợp, bạn có thể xử lý các tình huống mỗi khi thay đổi kết xuất xảy ra. Ngoài ra, bạn có thể nghĩ đến việc so sánh pđộ dài này (hoặc bất kỳ biện pháp nào khác) với mô hình của bạn nếu bạn muốn chạy mã của mình chỉ một lần sau khi kết xuất hoàn tất. Tôi đánh giá cao bất kỳ suy nghĩ / ý kiến ​​về điều này.


0

Bạn có thể sử dụng mô-đun 'Truyền qua jQuery' của các tiện ích góc cạnh . Tôi đã liên kết thành công một plugin băng chuyền cảm ứng jQuery với một số hình ảnh mà tôi truy xuất async từ một dịch vụ web và hiển thị chúng với ng-repeat.

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.