AngularJS加载完成后发送事件

Lio*_*ior 113 angularjs angularjs-scope

当所有指令完成编译/链接时,想知道检测页面加载/引导完成的最佳方法是什么.

有没有活动?我应该重载引导功能吗?

tru*_*ktr 201

只是预感:为什么不看看ngCloak指令是如何做到的呢?显然,ngCloak指令设法在事物加载后显示内容.我打赌看看ngCloak会得出确切的答案......

编辑1小时后: 好吧,好吧,我看了ngCloak,它真的很短.这显然意味着编译函数在{{template}}表达式被评估之前不会被执行(即它加载的模板),因此ngCloak指令的功能很好.

我的有根据的猜测是只使用与ngCloak相同的简单性来制作一个指令,然后在你的编译函数中做你想做的任何事情.:)将指令放在应用程序的根元素上.您可以调用类似myOnload的指令,并将其用作my-onload属性.编译模板(编译表达式并加载子模板)后,将执行编译功能.

编辑,23小时后: 好的,所以我做了一些研究,我也问了自己的问题.我问的问题与这个问题间接相关,但它巧合地引出了解决这个问题的答案.

答案是你可以创建一个简单的指令,并将你的代码放在指令的链接函数中,当你的元素准备好/加载时,它将运行(对于大多数用例,如下所述).基于Josh对编译和链接函数执行顺序的描述,

如果你有这个标记:

<div directive1>
  <div directive2>
    <!-- ... -->
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

然后AngularJS将通过以特定顺序运行指令函数来创建指令:

directive1: compile
  directive2: compile
directive1: controller
directive1: pre-link
  directive2: controller
  directive2: pre-link
  directive2: post-link
directive1: post-link
Run Code Online (Sandbox Code Playgroud)

默认情况下,直接"链接"函数是一个后链接,因此外部指令1的链接函数在内部指令2的链接函数运行之后才会运行.这就是为什么我们说在后链接中进行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.
            }
        };
    }]);
Run Code Online (Sandbox Code Playgroud)

现在您可以做的是将ngElementReady指令放到应用程序的根元素上,并在console.log加载时触发:

<body data-ng-app="MyApp" data-ng-element-ready="">
   ...
   ...
</body>
Run Code Online (Sandbox Code Playgroud)

就这么简单!只需制作一个简单的指令并使用它.;)

您可以进一步自定义它,以便它可以通过添加它来执行表达式(即函数)$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.
            }
        };
    }]);
Run Code Online (Sandbox Code Playgroud)

然后你可以在任何元素上使用它:

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

只需确保您的元素所在的范围(在控制器中)中定义了您的函数(例如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>
Run Code Online (Sandbox Code Playgroud)

而不是这个:

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

ngElementReady指令将在后一个示例中编译,但它的链接函数将不会被执行.注意:指令总是被编译,但是它们的链接函数并不总是执行,具体取决于上面的某些情况.

编辑,几分钟后:

哦,完全回答这个问题,你现在可以$emit或者$broadcast从在执行表达式或函数的事件ng-element-ready属性.:)例如:

<div data-ng-element-ready="$emit('someEvent')">
    ...
<div>
Run Code Online (Sandbox Code Playgroud)

编辑,甚至几分钟后:

@satchmorun的答案也适用,但仅适用于初始加载.这是一个非常有用的SO问题,描述了执行的顺序,包括链接功能app.run,以及其他.因此,根据您的使用情况,app.run可能会很好,但不适用于特定元素,在这种情况下,链接功能更好.

编辑,五个月后,10月17日太平洋标准时间8:11:

这不适用于异步加载的部分.您需要在部分中添加簿记(例如,一种方法是使每个部分跟踪其内容何时完成加载然后发出事件,以便父作用域可以计算已加载了多少部分并最终执行它需要的内容加载完所有部分后再做).

编辑,10月23日太平洋标准时间晚上10:52:

我做了一个简单的指令,用于在加载图像时触发一些代码:

/*
 * 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);
          }
        });
      }
    };
  });
Run Code Online (Sandbox Code Playgroud)

编辑,10月24日上午12:48太平洋标准时间:

我改进了我的原始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);
      }
    }
  }
}]);
Run Code Online (Sandbox Code Playgroud)

例如,someFunction当元素加载{{placeholders}}但尚未替换时,使用它来触发:

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

someFunction将在所有item.property占位符被替换之前调用.

根据需要评估尽可能多的表达式,并使最后一个表达式true等待{{placeholders}}如下计算:

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

someFunction并且anotherFunction{{placeholders}}被替换后将被解雇.

这仅在第一次加载元素时起作用,而不是在将来的更改中起作用.如果$digest在占位符最初被替换之后不断发生($ digest可能发生多达10次,直到数据停止更改),它可能无法按预期工作.它适用于绝大多数用例.

编辑,10月31日太平洋标准时间下午7:26:

好吧,这可能是我的最后和最后更新.这可能适用于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: 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);
      }
    }
  };
}]);
Run Code Online (Sandbox Code Playgroud)

像这样使用它

<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>
Run Code Online (Sandbox Code Playgroud)

当然,它可能会被优化,但我会留下它.requestAnimationFrame很不错.

  • 值得投票支持这个答案所花费的时间和精力.干得好! (45认同)
  • 很好的答案,但请考虑删除所有"编辑"行并重新调整您的答案.编辑历史记录可通过答案底部的"编辑..."链接获得,并且在阅读时会分散注意力. (7认同)
  • 对所有那些"数据 - "前缀真的很烦.我很高兴我不会自己使用它们. (3认同)
  • 源代码非常有用.如果你能在npm上公开,那就完美了.非常好的答案,非常好的解释,+1为这个努力的大量努力. (2认同)

sat*_*run 38

文档中angular.Module,有一个描述run函数的条目:

使用此方法注册在完成注射器加载所有模块时应执行的工作.

所以如果你有一些模块是你的应用程序:

var app = angular.module('app', [/* module dependencies */]);
Run Code Online (Sandbox Code Playgroud)

你可以在模块加载后运行东西:

app.run(function() {
  // Do post-load initialization stuff here
});
Run Code Online (Sandbox Code Playgroud)

编辑:手动初始化到救援

所以有人指出,run当DOM准备就绪并且链接起来时,不会调用它.当$injector引用的模块ng-app加载了所有依赖项时,它会被调用,这与DOM编译步骤是分开的.

我又看了一下手动初始化,看来这应该可以解决问题.

我做了一个小提琴来说明.

HTML很简单:

<html>
    <body>
        <test-directive>This is a test</test-directive>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

注意缺少一个ng-app.我有一个指令可以做一些DOM操作,所以我们可以确定事情的顺序和时间.

像往常一样,创建一个模块:

var app = angular.module('app', []);
Run Code Online (Sandbox Code Playgroud)

这是指令:

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"); }
            };
        }
    };
});
Run Code Online (Sandbox Code Playgroud)

我们将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;
}
Run Code Online (Sandbox Code Playgroud)

在我们做任何事情之前,test-directiveDOM中不应该有一个类的元素,在我们完成之后应该有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'));
});
Run Code Online (Sandbox Code Playgroud)

这很简单.文档准备就绪后,angular.bootstrap使用应用程序的根元素和一组模块名称进行调用.

实际上,如果您将一个run函数附加到app模块,您将看到它在任何编译发生之前运行.

如果您运行小提琴并观看控制台,您将看到以下内容:

before (should be 0): 0 
Compiling test-directive 
Prelink
Postlink
after (should be 1): 1 <--- success!
Run Code Online (Sandbox Code Playgroud)

  • 我发现使用`$ timeout(initMyPlugins,0)`在我的指令中工作,我需要的所有html都在那里 (5认同)
  • 谢谢@satchmorun!但是run()在链接部分结束之前执行 - 只是用一些console.logs验证它. (2认同)

Lio*_*ior 16

Angular没有提供一种在页面加载完成时发出信号的方法,可能是因为"完成"取决于你的应用程序.例如,如果您有部分的分层树,则加载其他部分."完成"意味着所有这些都已加载.任何框架都很难分析您的代码并理解所有内容已完成或仍在等待.为此,您必须提供特定于应用程序的逻辑来检查和确定.


The*_*heo 14

我已经提出了一种在评估角度初始化完成时相对准确的解决方案.

该指令是:

.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);
                    });
                }
            };
        }]);
Run Code Online (Sandbox Code Playgroud)

然后可以将其作为属性添加到body元素中,然后监听使用$scope.$on('initialised', fn)

它的工作原理是假设当没有更多的$ digest周期时应用程序被初始化.$ watch在每个摘要周期调用,因此启动计时器(setTimeout不是$ timeout,因此不会触发新的摘要周期).如果在超时内没有发生摘要循环,则假定应用程序已初始化.

它显然不如satchmoruns解决方案准确(因为摘要周期可能比超时更长)但我的解决方案不需要您跟踪模块,这使得它更容易管理(特别是对于大型项目).无论如何,似乎足够准确满足我的要求.希望能帮助到你.


Jor*_*ole 10

如果您使用的是Angular UI路由器,则可以侦听该$viewContentLoaded事件.

"$ viewContentLoaded - 在渲染DOM之后加载视图时触发.视图的'$ scope'发出事件." - 链接

$scope.$on('$viewContentLoaded', 
function(event){ ... });
Run Code Online (Sandbox Code Playgroud)

  • $ scope.$ watch('$ viewContentLoaded',function()为我做了伎俩 (3认同)
  • 为'你应该成为'而投票.如果我说"如果你使用React而不是Angular(你应该是)......",该怎么办?在这个生态系统中没有非常态度的恕我直言. (2认同)
  • 正如Angular University在一个不同的答案中指出的那样,$ viewContentLoaded最初可能不存在,但是现在它以完全相同的方式在内置的ngRoute提供程序中工作。考虑到这一点,我认为这是许多(大多数?)未来读者将要寻找的快速,简单,易读的答案。 (2认同)