[angularjs] AngularJS가로드를 마쳤을 때 이벤트 보내기

모든 지시문이 컴파일 / 링크를 완료했을 때 페이지 로딩 / 부트 스트랩의 완료를 감지하는 가장 좋은 방법이 무엇인지 궁금했습니다.

이미 이벤트가 있습니까? 부트 스트랩 기능을 오버로드해야합니까?



답변

직감 : 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

기본적으로 직선 “링크”함수는 사후 링크이므로 외부 지시문 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.
            }
        };
    }]);

이제 할 수있는 일은 앱의 루트 요소에 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와 같은 특정 지시문을 사용할 때는주의하십시오. 그들은 자신의 범위를 만들고 지시문이 실행되지 않을 수 있습니다. 예를 들어 ngIf가있는 요소에 새 ngElementReady 지시문을 배치하고 ngIf의 조건이 false로 평가되면 ngElementReady 지시문이로드되지 않습니다. 또는 예를 들어 ngInclude 지시문이있는 요소에 새 ngElementReady 지시문을 배치하면 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좋을 수도 있지만 특정 요소에는 적합하지 않을 수 있습니다.이 경우 링크 기능이 더 좋습니다.

수정, 5 개월 후, 10 월 17 일 8:11 PST :

비동기 적으로로드 된 부분은 작동하지 않습니다. 부분에 부기를 추가해야합니다 (예 : 한 가지 방법은 각 부분이 콘텐츠로드가 완료되는시기를 추적 한 다음 이벤트를 생성하여 부모 범위가 몇 개의 부분이로드되었는지 계산하고 마지막으로 필요한 작업을 수행 할 수 있도록하는 것입니다. 모든 부분이로드 된 후 수행).

수정, 10 월 23 일 오후 10시 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);
          }
        });
      }
    };
  });

수정, 10 월 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자리 표시자를 처음 교체 한 후에도 계속 발생 하면 원하는대로 작동하지 않을 수 있습니다 ($ digest는 데이터 변경이 중지 될 때까지 최대 10 번 발생할 수 있음). 대부분의 사용 사례에 적합합니다.

수정, 10 월 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'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 이 좋습니다.


답변

에 대한 문서angular.Module 에는 run함수를 설명하는 항목이 있습니다.

이 메서드를 사용하여 인젝터가 모든 모듈로드를 완료 할 때 수행해야하는 작업을 등록합니다.

따라서 앱에 해당하는 모듈이있는 경우 :

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

모듈이로드 된 후 다음을 실행할 수 있습니다.

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

편집 : 구조에 수동 초기화

그래서 runDOM이 준비되고 연결되었을 때는 호출되지 않는다는 지적 이있었습니다. 에서 $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태그를 divof class 로 바꾸고 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-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'));
});

매우 간단합니다. 문서가 준비되면 angular.bootstrap앱의 루트 요소와 모듈 이름 배열을 사용하여 호출 합니다.

실제로 모듈에 함수를 연결runapp 하면 컴파일이 발생하기 전에 실행되는 것을 볼 수 있습니다.

바이올린을 실행하고 콘솔을 보면 다음이 표시됩니다.

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


답변

Angular는 페이지로드가 완료되면 신호를 보내는 방법을 제공하지 않았습니다. 아마도 ‘완료’는 애플리케이션에 따라 달라 지기 때문일 수 있습니다 . 예를 들어 계층 적 부분 트리가있는 경우 하나는 다른 하나를로드합니다. “Finish”는 모두로드되었음을 의미합니다. 모든 프레임 워크는 코드를 분석하고 모든 것이 완료되었거나 여전히 기다려야한다는 것을 이해하는 데 어려움을 겪을 것입니다. 이를 위해서는 애플리케이션 별 로직을 제공하여이를 확인하고 결정해야합니다.


답변

각도 초기화가 완료 될 때를 평가할 때 상대적으로 정확한 솔루션을 생각해 냈습니다.

지시문은 다음과 같습니다.

.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)

$ digest주기가 더 이상 없을 때 애플리케이션이 초기화된다고 가정하여 작동합니다. $ watch는 모든 다이제스트 사이클을 호출하므로 타이머가 시작됩니다 ($ timeout이 아닌 setTimeout이므로 새 다이제스트 사이클이 트리거되지 않음). 타임 아웃 내에 다이제스트주기가 발생하지 않으면 애플리케이션이 초기화 된 것으로 간주됩니다.

분명히 satchmoruns 솔루션만큼 정확하지는 않지만 (다이제스트주기가 시간 초과보다 오래 걸릴 수 있으므로) 내 솔루션은 관리하기 훨씬 쉽게 만드는 모듈을 추적 할 필요가 없습니다 (특히 대규모 프로젝트의 경우) ). 어쨌든 내 요구 사항에 충분히 정확한 것 같습니다. 도움이 되었기를 바랍니다.


답변

Angular UI Router를 사용 하는 $viewContentLoaded경우 이벤트를 수신 할 수 있습니다 .

“$ viewContentLoaded- DOM이 렌더링 된 후 뷰가로드되면 시작됩니다 . 뷰의 ‘$ scope’가 이벤트를 내 보냅니다.” – 링크

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


답변

나는 JQuery로 각도의 DOM 조작을 관찰하고 내 앱에 대한 마무리를 설정했습니다 (내 앱 개요에 필요한 일종의 미리 정의되고 만족스러운 상황). 예를 들어 ng-repeater가 7 결과를 생성 할 것으로 기대합니다. 이를 위해 setInterval의 도움으로 관찰 함수를 설정합니다.

$(document).ready(function(){

  var interval = setInterval(function(){

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

  },50);

});


답변

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의 대답 에 따르면 우선 순위가 가장 낮습니다. 게다가 $ timeout 은 Angular가 콜백 실행 전에 전체 이벤트 루프를 실행하도록합니다.

$ rootScope 는 응용 프로그램의 모든 범위에 지시문을 배치하고 필요한 리스너에게만 알릴 수 있기 때문에 사용됩니다.

$ rootScope. $ emit은 모든 $ rootScope. $ on 리스너에 대해서만 이벤트를 발생시킵니다. 흥미로운 부분은 $ rootScope. $ 방송은 모든 $의 rootScope을 알려 것입니다. 물론 $ 범위로에 $. 청취자에 $
소스