กำลังส่งเหตุการณ์เมื่อ AngularJS โหลดเสร็จ


114

สงสัยว่าอะไรคือวิธีที่ดีที่สุดในการตรวจจับการสิ้นสุดของการโหลดหน้า / การบูตสตราปเมื่อคำสั่งทั้งหมดเสร็จสิ้นการรวบรวม / เชื่อมโยง

มีเหตุการณ์ใดแล้ว? ฉันควรใช้ฟังก์ชัน bootstrap มากเกินไปหรือไม่

คำตอบ:


204

แค่ลางสังหรณ์: ทำไมไม่ลองดูว่าคำสั่ง ngCloak ทำอย่างไร? เห็นได้ชัดว่าคำสั่ง ngCloak จัดการเพื่อแสดงเนื้อหาหลังจากโหลดสิ่งต่างๆแล้ว ฉันพนันได้เลยว่าการดู ngCloak จะนำไปสู่คำตอบที่แน่นอน ...

แก้ไข 1 ชั่วโมงต่อมา: โอเคฉันดูngCloakแล้วมันสั้นมาก สิ่งที่เห็นได้ชัดคือฟังก์ชันคอมไพล์จะไม่ถูกเรียกใช้งานจนกว่านิพจน์ {{template}} จะได้รับการประเมิน (เช่นเทมเพลตที่โหลด) ดังนั้นฟังก์ชันการทำงานที่ดีของคำสั่ง ngCloak

การคาดเดาที่มีการศึกษาของฉันคือเพียงสร้างคำสั่งด้วยความเรียบง่ายแบบเดียวกันของ ngCloak จากนั้นในฟังก์ชันคอมไพล์ของคุณให้ทำสิ่งที่คุณต้องการทำ :) วางคำสั่งไว้ที่องค์ประกอบหลักของแอปของคุณ คุณสามารถเรียกคำสั่งบางอย่างเช่น myOnload และใช้เป็นแอตทริบิวต์ my-onload ฟังก์ชันคอมไพล์จะดำเนินการเมื่อรวบรวมเทมเพลตแล้ว (นิพจน์ที่ประเมินและโหลดเทมเพลตย่อยแล้ว)

แก้ไข 23 ชั่วโมงต่อมา: โอเคฉันจึงค้นคว้าและถามคำถามของตัวเองด้วย คำถามที่ฉันถามเกี่ยวข้องโดยอ้อมกับคำถามนี้ แต่บังเอิญนำฉันไปสู่คำตอบที่ช่วยแก้คำถามนี้ได้

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

หากคุณมีมาร์กอัปนี้:

<div directive1>
  <div directive2>
    <!-- ... -->
  </div>
</div>

จากนั้น AngularJS จะสร้างคำสั่งโดยเรียกใช้ฟังก์ชันคำสั่งตามลำดับที่กำหนด:

directive1: compile
  directive2: compile
directive1: controller
directive1: pre-link
  directive2: controller
  directive2: pre-link
  directive2: post-link
directive1: post-link

ตามค่าเริ่มต้นฟังก์ชัน "ลิงก์" แบบตรงคือโพสต์ลิงก์ดังนั้นฟังก์ชันลิงก์ของ directive1 ภายนอกของคุณจะไม่ทำงานจนกว่าฟังก์ชันลิงก์ของ directive2 ภายในจะทำงาน นั่นเป็นเหตุผลที่เราบอกว่าการจัดการ DOM ในโพสต์ลิงก์นั้นปลอดภัยเท่านั้น ดังนั้นในคำถามเดิมไม่ควรมีปัญหาในการเข้าถึง html ภายในของคำสั่งเด็กจากฟังก์ชันลิงก์ของคำสั่งภายนอกแม้ว่าจะต้องรวบรวมเนื้อหาที่แทรกแบบไดนามิกตามที่กล่าวไว้ข้างต้น

จากสิ่งนี้เราสามารถสรุปได้ว่าเราสามารถสร้างคำสั่งเพื่อรันโค้ดของเราได้เมื่อทุกอย่างพร้อม / คอมไพล์ / เชื่อมโยง / โหลด:

    app.directive('ngElementReady', [function() {
        return {
            priority: -1000, // a low number so this directive loads after all other directives have loaded. 
            restrict: "A", // attribute only
            link: function($scope, $element, $attributes) {
                console.log(" -- Element ready!");
                // do what you want here.
            }
        };
    }]);

ตอนนี้สิ่งที่คุณทำได้คือวางคำสั่ง ngElementReady ลงในองค์ประกอบรูทของแอพและconsole.logจะเริ่มทำงานเมื่อโหลด:

<body data-ng-app="MyApp" data-ng-element-ready="">
   ...
   ...
</body>

มันง่ายมาก! เพียงแค่สร้างคำสั่งง่ายๆและใช้มัน ;)

คุณสามารถปรับแต่งเพิ่มเติมเพื่อให้สามารถเรียกใช้นิพจน์ (เช่นฟังก์ชัน) ได้โดยเพิ่ม$scope.$eval($attributes.ngElementReady);เข้าไป:

    app.directive('ngElementReady', [function() {
        return {
            priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
            restrict: "A",
            link: function($scope, $element, $attributes) {
                $scope.$eval($attributes.ngElementReady); // execute the expression in the attribute.
            }
        };
    }]);

จากนั้นคุณสามารถใช้กับองค์ประกอบใดก็ได้:

<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
    ...
    <div data-ng-element-ready="divIsReady()">...<div>
</body>

ตรวจสอบให้แน่ใจว่าคุณมีฟังก์ชันของคุณ (เช่น bodyIsReady และ divIsReady) ที่กำหนดไว้ในขอบเขต (ในตัวควบคุม) ที่องค์ประกอบของคุณอาศัยอยู่

คำเตือน: ฉันบอกว่าสิ่งนี้ใช้ได้กับกรณีส่วนใหญ่ ระมัดระวังเมื่อใช้คำสั่งบางอย่างเช่น ngRepeat และ ngIf พวกเขาสร้างขอบเขตของตนเองและคำสั่งของคุณอาจไม่เริ่มทำงาน ตัวอย่างเช่นถ้าคุณใส่คำสั่ง ngElementReady ใหม่ของเราในองค์ประกอบที่มี ngIf ด้วยและเงื่อนไขของ ngIf ประเมินว่าเป็นเท็จคำสั่ง ngElementReady ของเราจะไม่ถูกโหลด หรือตัวอย่างเช่นหากคุณใส่คำสั่ง ngElementReady ใหม่ของเราในองค์ประกอบที่มีคำสั่ง ngInclude ด้วยคำสั่งของเราจะไม่ถูกโหลดหากไม่มีเทมเพลตสำหรับ ngInclude คุณสามารถแก้ไขปัญหาเหล่านี้ได้โดยตรวจสอบให้แน่ใจว่าคุณซ้อนคำสั่งแทนที่จะวางทั้งหมดไว้ในองค์ประกอบเดียวกัน ตัวอย่างเช่นโดยทำสิ่งนี้:

<div data-ng-element-ready="divIsReady()">
    <div data-ng-include="non-existent-template.html"></div>
<div>

แทนสิ่งนี้:

<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>

คำสั่ง ngElementReady จะถูกคอมไพล์ในตัวอย่างหลัง แต่ฟังก์ชันลิงค์จะไม่ถูกเรียกใช้งาน หมายเหตุ: คำสั่งจะถูกคอมไพล์เสมอ แต่ฟังก์ชันลิงก์จะไม่ถูกเรียกใช้งานเสมอไปขึ้นอยู่กับสถานการณ์บางอย่างเช่นข้างต้น

แก้ไขไม่กี่นาทีต่อมา:

โอ้และเพื่อตอบคำถามอย่างเต็มที่ตอนนี้คุณสามารถ$emitหรือ$broadcastเหตุการณ์ของคุณจากนิพจน์หรือฟังก์ชันที่ดำเนินการในng-element-readyแอตทริบิวต์ :) เช่น:

<div data-ng-element-ready="$emit('someEvent')">
    ...
<div>

แก้ไขมากยิ่งขึ้นในอีกไม่กี่นาทีต่อมา:

คำตอบของ @ satchmorun ก็ใช้ได้เช่นกัน แต่สำหรับการโหลดครั้งแรกเท่านั้น ต่อไปนี้เป็นคำถาม SO ที่มีประโยชน์มากซึ่งอธิบายถึงลำดับการดำเนินการรวมถึงฟังก์ชันลิงก์app.runและอื่น ๆ ดังนั้นขึ้นอยู่กับกรณีการใช้งานของคุณapp.runอาจดี แต่ไม่ใช่สำหรับองค์ประกอบเฉพาะซึ่งในกรณีนี้ฟังก์ชันลิงก์จะดีกว่า

แก้ไขห้าเดือนต่อมา 17 ต.ค. เวลา 8:11 PST:

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

แก้ไข 23 ต.ค. เวลา 22:52 น. PST:

ฉันสร้างคำสั่งง่ายๆสำหรับการยิงโค้ดเมื่อโหลดรูปภาพ:

/*
 * This img directive makes it so that if you put a loaded="" attribute on any
 * img element in your app, the expression of that attribute will be evaluated
 * after the images has finished loading. Use this to, for example, remove
 * loading animations after images have finished loading.
 */
  app.directive('img', function() {
    return {
      restrict: 'E',
      link: function($scope, $element, $attributes) {
        $element.bind('load', function() {
          if ($attributes.loaded) {
            $scope.$eval($attributes.loaded);
          }
        });
      }
    };
  });

แก้ไข 24 ต.ค. เวลา 00:48 น. PST:

ฉันดีขึ้นเดิมของฉันสั่งและเปลี่ยนชื่อเป็นเพื่อngElementReadywhenReady

/*
 * The whenReady directive allows you to execute the content of a when-ready
 * attribute after the element is ready (i.e. done loading all sub directives and DOM
 * content except for things that load asynchronously like partials and images).
 *
 * Execute multiple expressions by delimiting them with a semi-colon. If there
 * is more than one expression, and the last expression evaluates to true, then
 * all expressions prior will be evaluated after all text nodes in the element
 * have been interpolated (i.e. {{placeholders}} replaced with actual values). 
 *
 * Caveats: if other directives exists on the same element as this directive
 * and destroy the element thus preventing other directives from loading, using
 * this directive won't work. The optimal way to use this is to put this
 * directive on an outer element.
 */
app.directive('whenReady', ['$interpolate', function($interpolate) {
  return {
    restrict: 'A',
    priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
    link: function($scope, $element, $attributes) {
      var expressions = $attributes.whenReady.split(';');
      var waitForInterpolation = false;

      function evalExpressions(expressions) {
        expressions.forEach(function(expression) {
          $scope.$eval(expression);
        });
      }

      if ($attributes.whenReady.trim().length == 0) { return; }

      if (expressions.length > 1) {
        if ($scope.$eval(expressions.pop())) {
          waitForInterpolation = true;
        }
      }

      if (waitForInterpolation) {
        requestAnimationFrame(function checkIfInterpolated() {
          if ($element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}}
            requestAnimationFrame(checkIfInterpolated);
          }
          else {
            evalExpressions(expressions);
          }
        });
      }
      else {
        evalExpressions(expressions);
      }
    }
  }
}]);

ตัวอย่างเช่นใช้วิธีนี้เพื่อเริ่มการทำงานsomeFunctionเมื่อองค์ประกอบถูกโหลดและ{{placeholders}}ยังไม่ได้แทนที่:

<div when-ready="someFunction()">
  <span ng-repeat="item in items">{{item.property}}</span>
</div>

someFunctionจะถูกเรียกก่อนitem.propertyตัวยึดตำแหน่งทั้งหมดจะถูกแทนที่

ประเมินนิพจน์ได้มากเท่าที่คุณต้องการและสร้างนิพจน์สุดท้ายtrueเพื่อรอ{{placeholders}}การประเมินดังนี้:

<div when-ready="someFunction(); anotherFunction(); true">
  <span ng-repeat="item in items">{{item.property}}</span>
</div>

someFunctionและanotherFunctionจะถูกไล่ออกหลังจาก{{placeholders}}ถูกแทนที่

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

แก้ไข 31 ต.ค. เวลา 19:26 น. PST:

ได้เลยนี่อาจเป็นการอัปเดตครั้งสุดท้ายและครั้งสุดท้ายของฉัน สิ่งนี้อาจใช้ได้กับ 99.999 กรณีการใช้งานที่นั่น:

/*
 * The whenReady directive allows you to execute the content of a when-ready
 * attribute after the element is ready (i.e. when it's done loading all sub directives and DOM
 * content). See: /programming/14968690/sending-event-when-angular-js-finished-loading
 *
 * Execute multiple expressions in the when-ready attribute by delimiting them
 * with a semi-colon. when-ready="doThis(); doThat()"
 *
 * Optional: If the value of a wait-for-interpolation attribute on the
 * element evaluates to true, then the expressions in when-ready will be
 * evaluated after all text nodes in the element have been interpolated (i.e.
 * {{placeholders}} have been replaced with actual values).
 *
 * Optional: Use a ready-check attribute to write an expression that
 * specifies what condition is true at any given moment in time when the
 * element is ready. The expression will be evaluated repeatedly until the
 * condition is finally true. The expression is executed with
 * requestAnimationFrame so that it fires at a moment when it is least likely
 * to block rendering of the page.
 *
 * If wait-for-interpolation and ready-check are both supplied, then the
 * when-ready expressions will fire after interpolation is done *and* after
 * the ready-check condition evaluates to true.
 *
 * Caveats: if other directives exists on the same element as this directive
 * and destroy the element thus preventing other directives from loading, using
 * this directive won't work. The optimal way to use this is to put this
 * directive on an outer element.
 */
app.directive('whenReady', ['$interpolate', function($interpolate) {
  return {
    restrict: 'A',
    priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
    link: function($scope, $element, $attributes) {
      var expressions = $attributes.whenReady.split(';');
      var waitForInterpolation = false;
      var hasReadyCheckExpression = false;

      function evalExpressions(expressions) {
        expressions.forEach(function(expression) {
          $scope.$eval(expression);
        });
      }

      if ($attributes.whenReady.trim().length === 0) { return; }

    if ($attributes.waitForInterpolation && $scope.$eval($attributes.waitForInterpolation)) {
        waitForInterpolation = true;
    }

      if ($attributes.readyCheck) {
        hasReadyCheckExpression = true;
      }

      if (waitForInterpolation || hasReadyCheckExpression) {
        requestAnimationFrame(function checkIfReady() {
          var isInterpolated = false;
          var isReadyCheckTrue = false;

          if (waitForInterpolation && $element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}}
            isInterpolated = false;
          }
          else {
            isInterpolated = true;
          }

          if (hasReadyCheckExpression && !$scope.$eval($attributes.readyCheck)) { // if the ready check expression returns false
            isReadyCheckTrue = false;
          }
          else {
            isReadyCheckTrue = true;
          }

          if (isInterpolated && isReadyCheckTrue) { evalExpressions(expressions); }
          else { requestAnimationFrame(checkIfReady); }

        });
      }
      else {
        evalExpressions(expressions);
      }
    }
  };
}]);

ใช้แบบนี้ครับ

<div when-ready="isReady()" ready-check="checkIfReady()" wait-for-interpolation="true">
   isReady will fire when this {{placeholder}} has been evaluated
   and when checkIfReady finally returns true. checkIfReady might
   contain code like `$('.some-element').length`.
</div>

แน่นอนว่ามันสามารถปรับให้เหมาะสมได้ แต่ฉันจะปล่อยไว้อย่างนั้น requestAnimationFrameดีจัง


3
น่ารำคาญจริงๆกับคำนำหน้า "data-" เหล่านั้น ดีใจที่ไม่ได้ใช้เอง
stolsvik

1
@stolsvik heh ใช่ในเบราว์เซอร์ที่ทันสมัยที่สุดที่พวกเขาไม่ต้องการ
trusktr

49
สมควรได้รับการโหวตสำหรับระยะเวลาและความพยายามในคำตอบนี้ ทำได้ดีมาก!
GordyD

8
เป็นคำตอบที่ดี แต่โปรดพิจารณาลบบรรทัด "แก้ไข" ทั้งหมดและปรับโครงสร้างคำตอบของคุณเล็กน้อย ประวัติการแก้ไขสามารถดูได้ผ่านลิงก์ "แก้ไข ... " ที่ด้านล่างของคำตอบของคุณและจะรบกวนสมาธิขณะอ่าน
user247702

2
ซอร์สโค้ดจะช่วยได้มาก และถ้าคุณสามารถทำให้เป็นสาธารณะใน npm ได้นั่นก็จะสมบูรณ์แบบ คำตอบที่ดีจริงๆอธิบายได้ดีจริงๆ +1 สำหรับจำนวนมากของความพยายามที่ใส่ลงไปนี้
tfrascaroli

38

ในเอกสารangular.Moduleมีรายการที่อธิบายถึงrunฟังก์ชัน:

ใช้วิธีนี้เพื่อลงทะเบียนงานที่ควรทำเมื่อหัวฉีดโหลดโมดูลทั้งหมดเสร็จแล้ว

ดังนั้นหากคุณมีโมดูลที่เป็นแอปของคุณ:

var app = angular.module('app', [/* module dependencies */]);

คุณสามารถเรียกใช้สิ่งต่างๆได้หลังจากที่โมดูลโหลดด้วย:

app.run(function() {
  // Do post-load initialization stuff here
});

แก้ไข: การเริ่มต้นด้วยตนเองเพื่อช่วยเหลือ

ดังนั้นจึงถูกชี้ให้เห็นว่าrunจะไม่ถูกเรียกเมื่อ DOM พร้อมและเชื่อมโยง จะถูกเรียกเมื่อ$injectorโมดูลที่อ้างถึงโดยng-appโหลดการอ้างอิงทั้งหมดซึ่งแยกจากขั้นตอนการคอมไพล์ DOM

ฉันลองดูการเริ่มต้นด้วยตนเองอีกครั้งและดูเหมือนว่าสิ่งนี้ควรใช้เคล็ดลับ

ผมได้ทำไวโอลินที่จะแสดงให้เห็นถึง

HTML นั้นง่ายมาก:

<html>
    <body>
        <test-directive>This is a test</test-directive>
    </body>
</html>

สังเกตว่าไม่มีng-appไฟล์. และฉันมีคำสั่งที่จะจัดการ DOM บางอย่างเพื่อให้เรามั่นใจในลำดับและเวลาของสิ่งต่างๆ

ตามปกติโมดูลจะถูกสร้างขึ้น:

var app = angular.module('app', []);

และนี่คือคำสั่ง:

app.directive('testDirective', function() {
    return {
        restrict: 'E',
        template: '<div class="test-directive"><h1><div ng-transclude></div></h1></div>',
        replace: true,
        transclude: true,
        compile: function() {
            console.log("Compiling test-directive");
            return {
                pre: function() { console.log("Prelink"); },
                post: function() { console.log("Postlink"); }
            };
        }
    };
});

เราจะแทนที่test-directiveแท็กด้วยdivคลาสtest-directiveและรวมเนื้อหาไว้ในไฟล์h1.

ฉันได้เพิ่มฟังก์ชันคอมไพล์ที่ส่งคืนฟังก์ชันลิงก์ก่อนและหลังเพื่อให้เราสามารถดูได้ว่าสิ่งเหล่านี้ทำงานเมื่อใด

นี่คือส่วนที่เหลือของรหัส:

// The bootstrapping process

var body = document.getElementsByTagName('body')[0];

// Check that our directive hasn't been compiled

function howmany(classname) {
    return document.getElementsByClassName(classname).length;
}

ก่อนที่เราจะทำอะไรไม่ควรมีองค์ประกอบที่มีคลาสtest-directiveใน DOM และหลังจากที่เราทำเสร็จแล้วควรมี 1

console.log('before (should be 0):', howmany('test-directive'));

angular.element(document).ready(function() {
    // Bootstrap the body, which loades the specified modules
    // and compiled the DOM.
    angular.bootstrap(body, ['app']);

    // Our app is loaded and the DOM is compiled
    console.log('after (should be 1):', howmany('test-directive'));
});

ค่อนข้างตรงไปตรงมา เมื่อเอกสารพร้อมแล้วให้เรียกangular.bootstrapด้วยองค์ประกอบรากของแอปของคุณและอาร์เรย์ของชื่อโมดูล

ในความเป็นจริงถ้าคุณแนบrunฟังก์ชันเข้ากับappโมดูลคุณจะเห็นว่ามันถูกเรียกใช้ก่อนที่จะมีการคอมไพล์ใด ๆ

หากคุณเรียกใช้ซอและดูคอนโซลคุณจะเห็นสิ่งต่อไปนี้:

before (should be 0): 0 
Compiling test-directive 
Prelink
Postlink
after (should be 1): 1 <--- success!

2
ขอบคุณ @satchmorun! แต่เรียกใช้ () ดำเนินการก่อนสิ้นสุดส่วนการเชื่อมโยง - เพียงตรวจสอบด้วย console.logs บางส่วน
Lior

ฉันอยากรู้อยากเห็นตัวเอง ... ฉันมีคำสั่งที่สั่งให้ใช้ปลั๊กอิน jQuery DOM บางตัวrunยิงก่อนคำสั่งและเมื่อไฟทำงาน html ไม่ได้อยู่ที่นั่นทั้งหมด
charlietfl

@charlietfl - ฉันขุดเล็กน้อยในการบูตด้วยตนเองและเป็นวิธีที่ง่ายมากในการค้นหาสิ่งที่คำถามกำลังมองหา ฉันได้เพิ่มการแก้ไขที่ค่อนข้างยาวให้กับคำตอบเดิมของฉัน
satchmorun

5
ฉันพบว่าการใช้$timeout( initMyPlugins,0)งานภายในคำสั่งของฉัน html ทั้งหมดที่ฉันต้องการอยู่ที่นั่น
charlietfl

@satchmorun ดูติดตามนี้: stackoverflow.com/questions/14989161/…
Lior

16

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


15

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

คำสั่งคือ:

.directive('initialisation',['$rootScope',function($rootScope) {
            return {
                restrict: 'A',
                link: function($scope) {
                    var to;
                    var listener = $scope.$watch(function() {
                        clearTimeout(to);
                        to = setTimeout(function () {
                            console.log('initialised');
                            listener();
                            $rootScope.$broadcast('initialised');
                        }, 50);
                    });
                }
            };
        }]);

จากนั้นสามารถเพิ่มเป็นแอตทริบิวต์ของbodyองค์ประกอบแล้วฟังเพื่อใช้$scope.$on('initialised', fn)

ทำงานโดยสมมติว่าแอปพลิเคชันเริ่มต้นเมื่อไม่มีรอบการย่อย $ อีกต่อไป $ watch เรียกว่าทุก ๆ รอบการย่อยดังนั้นตัวจับเวลาจึงเริ่มทำงาน (setTimeout ไม่ใช่ $ timeout เพื่อไม่ให้เกิดรอบการย่อยใหม่) หากวงจรการแยกย่อยไม่เกิดขึ้นภายในระยะหมดเวลาแสดงว่าแอปพลิเคชันได้เริ่มต้นแล้ว

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


ทางออกที่ยอดเยี่ยม สำหรับโครงการที่โค้ดทั้งหมดในไฟล์บีบอัดหนึ่งหรือสองไฟล์ทำงานได้ดีมาก
merqlove

1
นี่เป็นวิธีแก้ปัญหาที่ยอดเยี่ยมในกรณีที่คุณมีโค้ดจำนวนมากใน jquery และคุณกำลังพยายามแปลงโค้ดเป็นเชิงมุมทีละขั้นตอนสิ่งนี้ก็สมเหตุสมผลดี
Mangesh Pimpalkar

11

หากคุณใช้Angular UI Routerคุณสามารถฟัง$viewContentLoadedเหตุการณ์ได้

"$ viewContentLoaded - เริ่มทำงานเมื่อมุมมองถูกโหลดหลังจากที่ DOM แสดงผล " $ scope "ของมุมมองจะแสดงเหตุการณ์" - ลิงค์

$scope.$on('$viewContentLoaded', 
function(event){ ... });

3
$ scope. $ watch ('$ viewContentLoaded', function () ทำเคล็ดลับให้ฉัน
Louis XIV

2
ลดลงสำหรับ 'สิ่งที่คุณควรจะเป็น' จะเกิดอะไรขึ้นถ้าฉันบอกว่า "ถ้าคุณใช้ React แทน Angular (ซึ่งคุณควรจะเป็น) ... "? ไม่ใช่ทัศนคติที่จะมีในระบบนิเวศนี้ IMHO
Valentin Waeselynck

@ValentinWaeselynck คุณพูดถูกจริงๆ ฉันแก้ไขคำตอบเพื่อลบอคติของฉัน
Jordan Skole

1
ทำงานให้ฉัน! ขอบคุณ. ฉันเพิ่มมันลงในฟังก์ชันการทำงานของฉันจริง ๆ แล้วเปลี่ยน $ ขอบเขตเป็น $ rootScope
JDavies

2
ตามที่ Angular University ชี้ให้เห็นในคำตอบที่แตกต่างกัน $ viewContentLoaded อาจไม่เคยมีมาก่อน แต่ตอนนี้ใช้งานได้ในตัวให้บริการ ngRoute ในตัวในลักษณะเดียวกัน ด้วยเหตุนี้ฉันจึงคิดว่านี่เป็นคำตอบที่รวดเร็วง่ายและอ่านได้ที่ผู้อ่านในอนาคตจำนวนมาก (ส่วนใหญ่?) จะมองหา
Kevin Crumley

3

ฉันสังเกตการจัดการ DOM ของเชิงมุมด้วย JQuery และฉันได้ตั้งค่าการสิ้นสุดสำหรับแอพของฉัน (สถานการณ์ที่กำหนดไว้ล่วงหน้าและน่าพอใจบางอย่างที่ฉันต้องการสำหรับแอพนามธรรมของฉัน) ตัวอย่างเช่นฉันคาดหวังว่า ng-repeater ของฉันจะให้ผลลัพธ์ 7 และสำหรับฉัน จะตั้งค่าฟังก์ชันการสังเกตด้วยความช่วยเหลือของ setInterval เพื่อจุดประสงค์นี้

$(document).ready(function(){

  var interval = setInterval(function(){

  if($("article").size() == 7){
     myFunction();
     clearInterval(interval);
  }

  },50);

});

3
ฉันจะไม่ทำเช่นนี้ การใช้ช่วงเวลาเพื่อตรวจสอบสิ่งที่เกิดขึ้นไม่ใช่แนวทางปฏิบัติที่ดีไม่สามารถปรับขนาดได้และมีวิธีอื่น ๆ ในการทำให้สิ่งต่างๆเกิดขึ้น ตัวจับเวลามีไว้สำหรับการทำงานที่เป็นรูปธรรมซึ่งจำเป็นต้องเกิดขึ้นหลังจากช่วงเวลาหนึ่งไม่ใช่เพื่อ "คาดเดา" เมื่อเนื้อหาหรือผลลัพธ์พร้อม
dudewad

ไม่ต้องพูดถึงว่าการใช้ตัวจับเวลา jquery กับแพลตฟอร์มเชิงมุมนั้นเป็นเชิงมุมที่มีประสิทธิผล - เชิงมุมมีคลาสการหมดเวลาและคุณควรใช้สิ่งนั้นไม่เช่นนั้นคุณกำลังคร่อมสองเฟรมเวิร์กและมันจะสับสนอย่างรวดเร็ว
dudewad

3

หากคุณไม่ได้ใช้โมดูลngRouteนั่นคือคุณไม่มีเหตุการณ์$ viewContentLoaded

คุณสามารถใช้วิธีคำสั่งอื่น:

    angular.module('someModule')
        .directive('someDirective', someDirective);

    someDirective.$inject = ['$rootScope', '$timeout']; //Inject services

    function someDirective($rootScope, $timeout){
        return {
            restrict: "A",
            priority: Number.MIN_SAFE_INTEGER, //Lowest priority
            link    : function(scope, element, attr){
                $timeout(
                    function(){
                        $rootScope.$emit("Some:event");
                    }
                );
            }
        };
    }

ตามคำตอบของ trusktrจึงมีลำดับความสำคัญต่ำสุด การหมดเวลาบวก$จะทำให้ Angular ทำงานผ่านลูปเหตุการณ์ทั้งหมดก่อนดำเนินการเรียกกลับ

ใช้ $ rootScopeเนื่องจากอนุญาตให้วางคำสั่งในขอบเขตใด ๆ ของแอปพลิเคชันและแจ้งผู้ฟังที่จำเป็นเท่านั้น

$ rootScope $ emit จะเริ่มเหตุการณ์สำหรับ $ rootScope $ ทั้งหมดสำหรับผู้ฟังเท่านั้น ส่วนที่น่าสนใจคือ $ rootScope $ broadcast จะแจ้ง $ rootScope ทั้งหมด $ on เช่นเดียวกับ $ scope $ บนผู้ฟัง Source


2

ตามที่ทีมเชิงมุมและปัญหา Github :

ตอนนี้เรามีเหตุการณ์ $ viewContentLoaded และ $ includeContentLoaded ที่ปล่อยออกมาใน ng-view และ ng-include ตามลำดับ ฉันคิดว่านี่เป็นเรื่องใกล้ตัวมากที่สุดเท่าที่เราจะสามารถรู้ได้เมื่อเรารวบรวมเสร็จแล้ว

จากสิ่งนี้ดูเหมือนว่าในขณะนี้ไม่สามารถทำได้ด้วยวิธีที่เชื่อถือได้มิฉะนั้น Angular จะจัดให้มีกิจกรรมนอกกรอบ

การบูตแอปหมายถึงการเรียกใช้วงจรการย่อยในขอบเขตรูทและยังไม่มีเหตุการณ์ที่เสร็จสิ้นของวงจรการย่อย

ตามเอกสารการออกแบบ Angular 2 :

เนื่องจากการย่อยหลายรายการจึงไม่สามารถระบุและแจ้งส่วนประกอบได้ว่าโมเดลมีเสถียรภาพ เนื่องจากการแจ้งเตือนสามารถเปลี่ยนแปลงข้อมูลเพิ่มเติมซึ่งสามารถเริ่มกระบวนการผูกใหม่ได้

ด้วยเหตุนี้ความจริงที่ว่าสิ่งนี้เป็นไปไม่ได้จึงเป็นสาเหตุหนึ่งที่ทำให้ตัดสินใจเขียนซ้ำใน Angular 2


2

ฉันมีส่วนที่ได้รับการโหลดหลังจาก / โดยบางส่วนหลักที่เข้ามาผ่านการกำหนดเส้นทาง

ฉันต้องการเรียกใช้ฟังก์ชันหลังจากโหลดส่วนย่อยนั้นแล้วและฉันไม่ต้องการเขียนคำสั่งใหม่และคิดว่าคุณสามารถใช้หน้าด้าน ngIf

ผู้ควบคุมบางส่วนของผู้ปกครอง:

$scope.subIsLoaded = function() { /*do stuff*/; return true; };

HTML ของส่วนย่อย

<element ng-if="subIsLoaded()"><!-- more html --></element>

1

หากคุณต้องการสร้าง JS ด้วยข้อมูลฝั่งเซิร์ฟเวอร์ (JSP, PHP) คุณสามารถเพิ่มตรรกะของคุณลงในบริการซึ่งจะโหลดโดยอัตโนมัติเมื่อโหลดคอนโทรลเลอร์ของคุณ

นอกจากนี้หากคุณต้องการตอบสนองเมื่อคำสั่งทั้งหมดเสร็จสิ้นการคอมไพล์ / การเชื่อมโยงคุณสามารถเพิ่มโซลูชันที่เสนอที่เหมาะสมด้านบนในตรรกะการเริ่มต้น

module.factory('YourControllerInitService', function() {

    // add your initialization logic here

    // return empty service, because it will not be used
    return {};
});


module.controller('YourController', function (YourControllerInitService) {
});

0

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

$routeProvider
.when("/news", {
    templateUrl: "newsView.html",
    controller: "newsController",
    resolve: {
        message: function(messageService){
            return messageService.getMessage();
    }
}

})

คลิกที่นี่เพื่อดูเอกสารฉบับเต็ม - มอบเครดิตให้กับ K. Scott Allen


0

ฉันสามารถช่วยคุณได้จากตัวอย่างนี้

ในกล่องแฟนซีที่กำหนดเองฉันแสดงเนื้อหาด้วยค่าที่ถูกแก้ไข

ในบริการในวิธีการแฟนซีบ็อกซ์ "เปิด" ฉันทำ

open: function(html, $compile) {
        var el = angular.element(html);
     var compiledEl = $compile(el);
        $.fancybox.open(el); 
      }

$ compile ส่งคืนข้อมูลที่คอมไพล์แล้ว คุณสามารถตรวจสอบข้อมูลที่รวบรวมได้

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