AngularJS: วิธีรันโค้ดเพิ่มเติมหลังจาก AngularJS แสดงผลเทมเพลตแล้ว?


182

ฉันมีเทมเพลต Angular ใน DOM เมื่อคอนโทรลเลอร์ของฉันได้รับข้อมูลใหม่จากบริการมันจะอัพเดตโมเดลในขอบเขต $ และแสดงเทมเพลตอีกครั้ง ดีมากจนถึงตอนนี้

ปัญหาคือฉันต้องทำงานพิเศษหลังจากเทมเพลตถูกแสดงอีกครั้งและอยู่ใน DOM (ในกรณีนี้คือปลั๊กอิน jQuery)

ดูเหมือนว่าจะมีเหตุการณ์ที่ต้องฟังเช่น AfterRender แต่ฉันไม่พบสิ่งใดเลย บางทีคำสั่งอาจเป็นหนทางไป แต่ดูเหมือนว่าจะยิงเร็วเกินไป

นี่คือ jsFiddle ที่สรุปปัญหาของฉัน: Fiddle-AngularIssue

== UPDATE ==

จากความคิดเห็นที่เป็นประโยชน์ฉันจึงเปลี่ยนไปใช้คำสั่งเพื่อจัดการการจัดการ DOM และใช้โมเดล $ watch ในคำสั่ง อย่างไรก็ตามฉันยังคงมีปัญหาพื้นฐานเดียวกันอยู่ โค้ดที่อยู่ในเหตุการณ์การเฝ้าดู $ เริ่มก่อนที่จะรวบรวมและแทรกเทมเพลตใน DOM ดังนั้นปลั๊กอิน jquery จะประเมินตารางที่ว่างอยู่เสมอ

ที่น่าสนใจถ้าฉันลบการเรียกแบบ async สิ่งที่ใช้งานได้ดีก็เป็นขั้นตอนในทิศทางที่ถูกต้อง

นี่คือ Fiddle ที่อัปเดตของฉันเพื่อสะท้อนการเปลี่ยนแปลงเหล่านี้: http://jsfiddle.net/uNREn/12/


ดูเหมือนว่าคล้ายกับคำถามที่ฉันมี stackoverflow.com/questions/11444494/… . บางทีบางสิ่งในนั้นสามารถช่วยได้
dnc253

คำตอบ:


39

โพสต์นี้เก่า แต่ฉันเปลี่ยนรหัสของคุณเป็น:

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

ดูjsfiddle

ฉันหวังว่ามันจะช่วยคุณ


ขอบคุณมันมีประโยชน์มาก นี่เป็นทางออกที่ดีที่สุดสำหรับฉันเพราะมันไม่ต้องการการจัดการ dom จากคอนโทรลเลอร์และจะยังคงทำงานได้เมื่อข้อมูลได้รับการอัปเดตจากการตอบสนองบริการแบบอะซิงโครนัสอย่างแท้จริง (และฉันไม่ต้องใช้ $ evalAsync ในคอนโทรลเลอร์ของฉัน)
gorebash

9
สิ่งนี้เลิกใช้แล้วหรือยัง? เมื่อฉันลองทำสิ่งนี้มันจะเป็นการเรียกก่อนที่ DOM จะถูกเรนเดอร์ มันขึ้นอยู่กับ shadow dom หรือบางอย่าง?
เชน

ฉันค่อนข้างใหม่กับ Angular และฉันไม่สามารถเห็นวิธีการใช้คำตอบนี้กับกรณีเฉพาะของฉัน ฉันได้ดูคำตอบต่าง ๆ และการคาดเดา แต่นั่นไม่ได้ผล ฉันไม่แน่ใจว่าฟังก์ชั่น 'ดู' ควรดูอะไร แอพเชิงมุมของเรามีคำสั่งที่แตกต่างหลากหลายซึ่งจะแตกต่างกันไปในแต่ละหน้าดังนั้นฉันจะรู้ได้อย่างไรว่า 'ดู'? แน่นอนมีวิธีง่ายๆในการเริ่มรหัสบางอย่างเมื่อหน้าเสร็จสิ้นการแสดง?
moosefetcher

63

ก่อนอื่นสถานที่ที่เหมาะสมที่จะยุ่งกับการเรนเดอร์คือคำสั่ง คำแนะนำของฉันคือการห่อ DOM การจัดการปลั๊กอิน jQuery โดยคำสั่งเช่นนี้

ฉันมีปัญหาเดียวกันและมาพร้อมกับตัวอย่างนี้ มันใช้$watchและ$evalAsyncเพื่อให้แน่ใจว่ารหัสของคุณทำงานหลังจากคำสั่งเช่นng-repeatได้รับการแก้ไขและแม่แบบเช่น{{ value }}ถูกแสดงผล

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

หวังว่านี่จะช่วยคุณป้องกันการดิ้นรน


นี่อาจเป็นการระบุที่ชัดเจน แต่ถ้ามันไม่ได้ผลสำหรับคุณให้ตรวจสอบการทำงานแบบอะซิงโครนัสในฟังก์ชั่นลิงค์ / คอนโทรลเลอร์ของคุณ ฉันไม่คิดว่า $ evalAsync จะสามารถพิจารณาสิ่งเหล่านั้นได้
LOAS

ที่น่าสังเกตว่าคุณยังสามารถรวมฟังก์ชั่นด้วยlink templateUrl
Wtower

35

ทำตามคำแนะนำของ Misko หากคุณต้องการการทำงานแบบ async แทนที่จะเป็น $ timeout ()

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

ใช้ $ evalAsync () (ใช้งานได้)

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

ซอ ฉันยังเพิ่มลิงก์ "ลบแถวข้อมูล" ที่จะแก้ไข $ scope.assignments จำลองการเปลี่ยนแปลงข้อมูล / รุ่น - เพื่อแสดงว่าการเปลี่ยนแปลงข้อมูลทำงาน

Runtimeส่วนหนึ่งของหน้าภาพรวมแนวคิดอธิบายว่า evalAsync ควรจะใช้เมื่อคุณต้องการสิ่งที่จะเกิดขึ้นนอกกรอบสแต็คในปัจจุบัน แต่ก่อนที่เบราว์เซอร์วาทกรรม (การคาดเดาที่นี่ ... "เฟรมสแต็กปัจจุบัน" อาจรวมถึงการอัปเดต Angular DOM) ใช้การหมดเวลา $ ถ้าคุณต้องการบางสิ่งบางอย่างที่จะเกิดขึ้นหลังจากเบราว์เซอร์แสดงผล

อย่างไรก็ตามตามที่คุณทราบแล้วฉันไม่คิดว่ามีความจำเป็นสำหรับการดำเนินการ async ที่นี่


4
$scope.$evalAsync($scope.assignmentsLoaded(data));ไม่สมเหตุสมผล IMO อาร์กิวเมนต์สำหรับ$evalAsync()ควรเป็นฟังก์ชัน ในตัวอย่างของคุณคุณกำลังเรียกฟังก์ชั่นในเวลาที่ฟังก์ชั่นควรจะลงทะเบียนสำหรับการดำเนินการในภายหลัง
hgoebl

@hgoebl อาร์กิวเมนต์สำหรับ$ evalAsyncสามารถเป็นฟังก์ชันหรือนิพจน์ได้ ในกรณีนี้ฉันใช้นิพจน์ แต่คุณพูดถูกการแสดงออกจะถูกดำเนินการทันที ฉันแก้ไขคำตอบเพื่อตัดคำในฟังก์ชันสำหรับการดำเนินการในภายหลัง ขอบคุณสำหรับการจับที่
Mark Rajcok

26

ฉันพบวิธีแก้ปัญหาที่ง่ายที่สุด (ราคาถูกและร่าเริง) เพียงเพิ่มช่องว่างด้วย ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing ()" ต่อท้ายองค์ประกอบสุดท้ายที่แสดงผล ฟังก์ชั่นนี้จะทำงานเมื่อมีการตรวจสอบว่าองค์ประกอบช่วงเวลาที่ควรจะแสดง รันรหัสอื่น ๆ ในฟังก์ชั่นนี้

ฉันรู้ว่านี่ไม่ใช่วิธีที่ยอดเยี่ยมที่สุดในการทำสิ่งต่าง ๆ แต่มันเหมาะกับฉัน ...

ฉันมีสถานการณ์ที่คล้ายกันแม้ว่าจะย้อนกลับเล็กน้อยที่ฉันต้องการลบตัวบ่งชี้การโหลดเมื่อภาพเคลื่อนไหวเริ่มขึ้นบนอุปกรณ์มือถือเชิงมุมเริ่มต้นได้เร็วกว่าภาพเคลื่อนไหวที่จะแสดงและการใช้ ng-cloak ไม่เพียงพอเนื่องจากตัวบ่งชี้การโหลดคือ นำออกได้ดีก่อนที่ข้อมูลจริงใด ๆ จะปรากฏขึ้น ในกรณีนี้ฉันเพิ่งเพิ่มฟังก์ชัน return 0 ของฉันไปยังองค์ประกอบที่แสดงผลแรกและในฟังก์ชั่นนั้นพลิก var ที่ซ่อนตัวบ่งชี้การโหลด (แน่นอนฉันเพิ่ม ng-hide เข้ากับตัวบ่งชี้การโหลดที่เรียกใช้โดยฟังก์ชันนี้


3
นี่คือแฮ็คอย่างแน่นอน แต่มันเป็นสิ่งที่ฉันต้องการ บังคับให้โค้ดของฉันทำงานอย่างแน่นอนหลังจากแสดงผล (หรือใกล้เคียงอย่างมาก) ในโลกอุดมคติฉันจะไม่ทำอย่างนี้ แต่ที่นี่เราอยู่ ...
Andrew

ฉันต้องการ$anchorScrollบริการในตัวที่จะเรียกใช้หลังจากการแสดงผล DOM และดูเหมือนว่าไม่มีอะไรที่จะทำเคล็ดลับ แต่สิ่งนี้ แฮ็ก แต่ทำงานได้
Pat Migliaccio

ng-init เป็นทางเลือกที่ดีกว่า
Flying Gambit

1
ng-init อาจไม่ทำงานเสมอไป ตัวอย่างเช่นฉันมีซ้ำ - ซ้ำที่เพิ่มจำนวนของภาพไปยัง Dom ถ้าฉันต้องการเรียกใช้ฟังก์ชั่นหลังจากเพิ่มภาพแต่ละภาพใน ng-repeat ฉันต้องใช้ ng-show
Michael Bremerkamp

7

ฉันคิดว่าคุณกำลังมองหา $ evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync


2
ขอบคุณสำหรับความคิดเห็น บางทีฉันใหม่เกินไปที่จะเป็น Angular แต่ฉันไม่เห็นวิธีการทำงานในรหัสของฉัน มีตัวอย่างใดบ้างที่คุณสามารถชี้ให้ฉันเห็นได้
gorebash

4

ในที่สุดฉันก็พบวิธีแก้ปัญหาฉันใช้บริการ REST เพื่ออัพเดทคอลเล็กชันของฉัน เพื่อแปลง jquery DataTable เป็นรหัสต่อไปนี้:

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

3

ฉันต้องทำสิ่งนี้บ่อยครั้ง ฉันมีคำสั่งและจำเป็นต้องทำสิ่ง jquery บางอย่างหลังจากสิ่งแบบจำลองถูกโหลดเข้าสู่ DOM ดังนั้นฉันใส่ตรรกะของฉันในลิงค์: ฟังก์ชั่นของคำสั่งและห่อรหัสใน setTimeout (ฟังก์ชั่น () {..... }, 1); setTimout จะเริ่มทำงานหลังจากโหลด DOM และ 1 milisecond เป็นระยะเวลาที่สั้นที่สุดหลังจากโหลด DOM แล้วก่อนที่โค้ดจะทำงาน ดูเหมือนว่าจะใช้งานได้สำหรับฉัน แต่ฉันต้องการให้เกิดเหตุการณ์เชิงมุมเมื่อมีการโหลดแม่แบบเพื่อให้คำสั่งที่ใช้โดยแม่แบบนั้นสามารถทำสิ่ง jquery และเข้าถึงองค์ประกอบ DOM หวังว่านี่จะช่วยได้



2

ทั้ง $ scope $ evalAsync () หรือ $ timeout (fn, 0) ทำงานได้อย่างน่าเชื่อถือสำหรับฉัน

ฉันต้องรวมทั้งสองอย่างเข้าด้วยกัน ฉันทำคำสั่งและวางลำดับความสำคัญสูงกว่าค่าเริ่มต้นสำหรับการวัดที่ดี นี่เป็นคำสั่งสำหรับมัน (หมายเหตุฉันใช้ ngInject เพื่อฉีดการพึ่งพา):

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

ในการเรียกคำสั่งคุณจะทำสิ่งนี้:

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

หากคุณต้องการโทรหาหลังจากที่ ng-repeat เสร็จสิ้นแล้วฉันจะเพิ่มการเว้นว่างใน ng-repeat และ 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

ในบางสถานการณ์ที่คุณอัปเดตบริการและเปลี่ยนเส้นทางไปยังมุมมองใหม่ (หน้า) จากนั้นคำสั่งของคุณจะถูกโหลดก่อนที่บริการของคุณจะได้รับการอัปเดตจากนั้นคุณสามารถใช้ $ rootScope ได้ $ Broadcast หาก $ watch หรือ $ timeout ล้มเหลว

ดู

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

ตัวควบคุม

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

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

}]);

คำสั่ง

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

ฉันมาพร้อมกับวิธีแก้ปัญหาที่ค่อนข้างง่าย ฉันไม่แน่ใจว่าเป็นวิธีที่ถูกต้องหรือไม่ แต่ใช้งานได้จริง เรามาดูสิ่งที่เราต้องการให้แสดงโดยตรง ตัวอย่างเช่นในคำสั่งที่รวมถึงบางng-repeatฉันจะระวังความยาวของข้อความ (คุณอาจมีสิ่งอื่น ๆ !) ของย่อหน้าหรือทั้ง HTML คำสั่งจะเป็นเช่นนี้:

.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
            });
        }
}]);

โปรดทราบว่ารหัสจะทำงานหลังจากการเรนเดอร์การเปลี่ยนแปลงค่อนข้างจะเรนเดอร์เสร็จสมบูรณ์ แต่ฉันเดาว่าในกรณีส่วนใหญ่คุณสามารถรับมือกับสถานการณ์เมื่อใดก็ตามที่การเปลี่ยนแปลงเกิดขึ้น นอกจากนี้คุณอาจลองเปรียบเทียบpความยาวนี้(หรือการวัดอื่น ๆ ) กับแบบจำลองของคุณหากคุณต้องการเรียกใช้รหัสของคุณเพียงครั้งเดียวหลังจากแสดงผลเสร็จสมบูรณ์ ฉันขอขอบคุณความคิด / ความคิดเห็นใด ๆ เกี่ยวกับเรื่องนี้


0

คุณสามารถใช้โมดูล 'jQuery Passthrough' ของutils เชิงมุม-UI ฉันผูกปลั๊กอินม้าหมุนแบบสัมผัส jQuery กับรูปภาพบางรูปที่ฉันเรียกใช้ async จากบริการเว็บและแสดงด้วย ng-repeat

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.