Отправка события при завершении загрузки angular.js

Удивительно, что лучший способ обнаружить завершение загрузки/загрузки страницы, когда все директивы выполняются компиляцией/компоновкой.

Любое событие уже существует? Должен ли я перегружать функцию начальной загрузки?

Ответ 1

Хотелось добавить еще одно осознание, которое у меня было о моем собственном вопросе: Angular не предоставил способ сообщить, когда страница закончила загрузку, возможно, потому что "завершено" зависит от вашего приложения. например, если у вас есть иерархическое дерево партитур, одна загрузка других. "finish" означает, что все они загружены. Любая инфраструктура будет нелегко анализировать ваш код и понимать, что все сделано или все еще ждет. Для этого вам придется предоставить логику приложения для проверки и определения этого.

спасибо

Лиор

Ответ 2

Просто догадайтесь: почему бы не посмотреть, как это делает директива ngCloak? Очевидно, директива ngCloak позволяет показывать контент после загрузки. Бьюсь об заклад, ngCloak приведет к точному ответу...

РЕДАКТИРОВАТЬ 1 час спустя: Хорошо, я посмотрел на ngCloak, и это действительно коротко. Очевидно, что это означает, что функция компиляции не будет выполняться до тех пор, пока выражения {{template}} не будут оценены (т.е. Загруженный шаблон), таким образом, хорошая функциональность директивы ngCloak.

Мое образованное предположение заключалось бы в том, чтобы просто создать директиву с той же простотой ngCloak, а затем в вашей функции компиляции сделайте все, что вы хотите сделать.:) Поместите директиву в корневой элемент вашего приложения. Вы можете вызвать директиву что-то вроде myOnload и использовать ее как атрибут my-onload. Функция компиляции будет выполняться после того, как шаблон был скомпилирован (обработаны выражения и подшаблоны).

EDIT, через 23 часа: Хорошо, поэтому я провел некоторое исследование, и я также задал свой вопрос. Вопрос, который я задал, косвенно связан с этим вопросом, но он случайно приводит меня к ответу, который решает этот вопрос.

Ответ заключается в том, что вы можете создать простую директиву и поместить свой код в функцию директивной ссылки, которая (для большинства случаев использования, описанная ниже) будет выполняться, когда ваш элемент будет готов/загружен. На основе Josh описание порядка выполнения компиляции и ссылок,

если у вас есть эта разметка:

<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

По умолчанию прямая функция "ссылка" - это пост-ссылка, поэтому ваш внешний Функция linkive1 не будет работать до тех пор, пока функция directive2 link побежала. Вот почему мы говорим, что это только безопасно выполнять манипуляции с 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 будет равно false, тогда наша директива 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 будет скомпилирована в последнем примере, но функция ссылки не будет выполнена. Примечание: директивы всегда скомпилированы, но их функции ссылок не всегда выполняются в зависимости от определенных сценариев, подобных приведенным выше.

EDIT, через несколько минут:

О, и чтобы полностью ответить на вопрос, вы можете теперь $emit или $broadcast ваше событие из выражения или функции, которая выполняется в атрибуте ng-element-ready.:) Например:

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

EDIT, еще несколько минут спустя:

Ответ на

@satchmorun тоже работает, но только для начальной загрузки. Здесь очень полезный вопрос SOкоторый описывает порядок выполнения заказов, включая функции ссылок, app.run и другие. Таким образом, в зависимости от вашего варианта использования app.run может быть хорошим, но не для определенных элементов, и в этом случае функции ссылок лучше.

EDIT, пять месяцев спустя, 17 октября в 8:11 PST:

Это не работает с частичными, которые загружаются асинхронно. Вам нужно будет добавить бухгалтерию в свои частичные (например, один из способов состоит в том, чтобы каждый частичный контроль, когда его содержимое загружается, затем выделяет событие, поэтому родительская область может подсчитывать, сколько частичных файлов загрузилось и, наконец, сделать то, что нужно после того, как все частичные файлы загружены).

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

EDIT, 24 октября в 12:48 PST:

Я улучшил свою оригинальную директиву ngElementReady и переименовал ее в whenReady.

/*
 * 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 раз, пока данные не перестанут меняться). Он будет подходящим для подавляющего большинства случаев использования.

EDIT, 31 октября в 7: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 done loading all sub directives and DOM
 * content). See: https://stackoverflow.com/questions/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

В docs для angular.Module есть запись, описывающая функцию run:

Используйте этот метод для регистрации работы, которая должна выполняться при загрузке инжектора при загрузке всех модулей.

Итак, если у вас есть модуль, который является вашим приложением:

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

Вы можете запускать материал после загрузки модулей:

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

EDIT: ручная инициализация на помощь

Итак, было указано, что 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.

Я добавил функцию компиляции, которая возвращает как функции pre, так и post link, чтобы мы могли видеть, когда эти вещи выполняются.

Здесь остальная часть кода:

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

Прежде чем что-либо сделать, в DOM не должно быть элементов с классом test-directive, и после того, как мы закончим, должно быть 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!

Ответ 4

Я придумал решение, которое относительно точно оценивает, когда завершается инициализация angular.

Директива:

.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 (как это возможно, цикл дайджест занимает больше времени, чем таймаут), но мое решение не требует от вас отслеживания модулей, что делает его намного проще в управлении (в частности, для крупных проектов). Во всяком случае, кажется, что я достаточно точен для своих требований. Надеюсь, что это поможет.

Ответ 5

Если вы используете Angular UI Router, вы можете прослушивать событие $viewContentLoaded.

"$ viewContentLoaded - запускается после загрузки представления, после рендеринга DOM. '$ scope' представления выдает событие." - Ссылка

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

Ответ 6

Я наблюдаю манипулирование DOM angular с помощью JQuery, и я установил финиш для своего приложения (какая-то предопределенная и удовлетворительная ситуация, которая мне нужна для моего абстрактного приложения), например, я ожидаю, что мой ng-повторитель будет генерировать 7 результат, и для этого я установлю функцию наблюдения с помощью setInterval для этой цели.

$(document).ready(function(){

  var interval = setInterval(function(){

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

  },50);

});

Ответ 7

В соответствии с командой Angular и проблема Github:

теперь мы имеем события $viewContentLoaded и $includeContentLoaded, которые испускаются в ng-view и ng-include соответственно. Я думаю, что это так близко, что можно узнать, когда мы закончили компиляцию.

Исходя из этого, кажется, что это в настоящее время невозможно сделать надежным способом, иначе Angular предоставил бы событие из коробки.

При загрузке приложения следует запустить цикл дайджеста в корневой области, а также не будет завершено событие цикла дайджест.

В соответствии с Angular 2 проектные документы:

Из-за нескольких дайджестов невозможно определить и оповестить компонент о стабильности модели. Это связано с тем, что уведомление может дополнительно изменять данные, которые могут перезапустить процесс привязки.

В соответствии с этим тот факт, что это невозможно, является одной из причин, почему было принято решение переписать в Angular 2.

Ответ 8

Если вы не используете 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 answer он имеет самый низкий приоритет. Плюс $timeout приведет к тому, что Angular будет проходить через весь цикл событий до выполнения обратного вызова.

$rootScope, поскольку он позволяет размещать директиву в любой области приложения и уведомлять только о необходимых слушателях.

$rootScope. $emit будет запускать событие для всех $rootScope. $только для слушателей. Интересная часть состоит в том, что $rootScope. $Broadcast будет уведомлять все $rootScope. $, А также $scope. $На слушателях Источник

Ответ 9

У меня был фрагмент, который загружался после/из основной части, которая поступала через маршрутизацию.

Мне нужно было запустить функцию после того, как этот субпартент загрузился, и я не хотел писать новую директиву и понял, что вы можете использовать нахальный ngIf

Контроллер родительского частичного:

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

HTML субчастичного

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

Ответ 10

Если вы хотите сгенерировать 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) {
});

Ответ 11

Все это отличные решения. Однако, если вы используете Routing, я нашел это решение самым легким и наименьшим количеством необходимого кода. Используя свойство "разрешение", чтобы дождаться завершения обещания до запуска маршрута. например.

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

})

Нажмите здесь, чтобы просмотреть все документы - Кредит К. Скотт Аллен

Ответ 12

Возможно, я могу помочь вам в этом примере

В пользовательском fancybox я показываю содержимое с интерполированными значениями.

в службе, в "открытом" методе fancybox, я делаю

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

компиляция $компилирует данные. вы можете проверить скомпилированные данные