development

AngularJS : AngularJS가 템플릿을 렌더링 한 후 추가 코드를 실행하는 방법은 무엇입니까?

big-blog 2020. 5. 20. 08:18
반응형

AngularJS : AngularJS가 템플릿을 렌더링 한 후 추가 코드를 실행하는 방법은 무엇입니까?


DOM에 Angular 템플릿이 있습니다. 컨트롤러가 서비스에서 새 데이터를 가져 오면 $ scope에서 모델을 업데이트하고 템플릿을 다시 렌더링합니다. 지금까지는 모두 좋았습니다.

문제는 템플릿을 다시 렌더링하고 DOM (이 경우 jQuery 플러그인)에 넣은 후에 추가 작업을 수행해야한다는 것입니다.

AfterRender와 같은 청취 이벤트가 있어야하는 것처럼 보이지만 그러한 것을 찾을 수 없습니다. 지시어가 갈 수있는 방법 일지 모르지만 너무 일찍 발사되는 것처럼 보였습니다.

여기 내 문제를 간략히 설명하는 jsFiddle이 있습니다. Fiddle-AngularIssue

== 업데이트 ==

유용한 의견을 바탕으로 DOM 조작을 처리하기 위해 지시문으로 전환했으며 지시문 안에 $ watch 모델을 구현했습니다. 그러나 여전히 동일한 기본 문제가 있습니다. $ watch 이벤트 내부의 코드는 템플릿이 컴파일되어 DOM에 삽입되기 전에 시작되므로 jquery 플러그인은 항상 빈 테이블을 평가합니다.

흥미롭게도 비동기 호출을 제거하면 모든 것이 잘 작동하므로 올바른 방향으로 나아가는 단계입니다.

다음은 이러한 변경 사항을 반영하기 위해 업데이트 된 Fiddle입니다. http://jsfiddle.net/uNREn/12/


이 게시물은 오래되었지만 코드를 다음과 같이 변경합니다.

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

jsfiddle을 참조하십시오 .

도움이 되길 바랍니다


먼저, 렌더링을 망칠 적절한 장소는 지시어입니다. 내 조언은 jQuery 플러그인을 조작하는 DOM을 이와 같은 지시문으로 감싸는 것입니다.

나는 같은 문제가 있었고이 발췌 문장을 생각해 냈습니다. 그것은 사용 $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);
                });
            });
        },
    };
});

이것이 약간의 투쟁을 예방하는 데 도움이되기를 바랍니다.


비동기 작업을 원한다면 Misko의 조언에 따라 $ timeout () 대신 작동하지 않습니다.

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

$ evalAsync () 사용하십시오 (작동합니다)

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

바이올린 . 또한 $ scope.assignments를 수정하여 데이터 / 모델 변경을 시뮬레이션하는 "데이터 행 제거"링크를 추가하여 데이터 변경이 작동 함을 보여줍니다.

개념적 개요 페이지 런타임 섹션에서는 현재 스택 프레임 외부에서 발생해야하지만 브라우저가 렌더링되기 전에 evalAsync를 사용해야한다고 설명합니다. (여기서 ... "현재 스택 프레임"에는 아마도 Angular DOM 업데이트가 포함되어있을 것입니다.) 브라우저가 렌더링 된 후에 무언가가 필요하면 $ timeout을 사용하십시오.

그러나 이미 알았 듯이 여기서 비동기 작업이 필요하지 않다고 생각합니다.


가장 간단한 (저렴하고 쾌활한) 솔루션은 마지막으로 렌더링 된 요소의 끝에 ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing ()"으로 빈 범위를 추가하는 것입니다. 이 기능은 span 요소가 표시되어야하는지 확인할 때 실행됩니다. 이 함수에서 다른 코드를 실행하십시오.

I realize this is not the most elegant way to do things, however, it works for me...

I had a similar situation, though slightly reversed where I needed to remove a loading indicator when an animation began, on mobile devices angular was initializing much faster than the animation to be displayed, and using an ng-cloak was insufficient as the loading indicator was removed well before any real data was displayed. In this case I just added the my return 0 function to the first rendered element, and in that function flipped the var that hides the loading indicator. (of course I added an ng-hide to the loading indicator triggered by this function.


I think you are looking for $evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync


Finally i found the solution, i was using a REST service to update my collection. In order to convert datatable jquery is the follow code:

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

i've had to do this quite often. i have a directive and need to do some jquery stuff after model stuff is fully loaded into the DOM. so i put my logic in the link: function of the directive and wrap the code in a setTimeout(function() { ..... }, 1); the setTimout will fire after the DOM is loaded and 1 milisecond is the shortest amount of time after DOM is loaded before code would execute. this seems to work for me but i do wish angular raised an event once a template was done loading so that directives used by that template could do jquery stuff and access DOM elements. hope this helps.


You can also create a directive that runs your code in the link function.

See that stackoverflow reply.


Neither $scope.$evalAsync() or $timeout(fn, 0) worked reliably for me.

I had to combine the two. I made a directive and also put a priority higher than the default value for good measure. Here's a directive for it (Note I use ngInject to inject dependencies):

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

To call the directive, you would do this:

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

If you want to call it after an ng-repeat is done running, I added an empty span in my ng-repeat and 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>

I came with a pretty simple solution. I'm not sure whether it is the correct way to do it but it works in a practical sense. Let's directly watch what we want to be rendered. For example in a directive that includes some ng-repeats, I would watch out for the length of text (you may have other things!) of paragraphs or the whole html. The directive will be like this:

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

NOTE that the code actually runs after rendering changes rather complete rendering. But I guess in most cases you can handle the situations whenever rendering changes happen. Also you could think of comparing this ps length (or any other measure) with your model if you want to run your code only once after rendering completed. I appreciate any thoughts/comments on this.


You can use the 'jQuery Passthrough' module of the angular-ui utils. I successfully binded a jQuery touch carousel plugin to some images that I retrieve async from a web service and render them with ng-repeat.


In some scenarios where you update a service and redirect to a new view(page) and then your directive gets loaded before your services are updated then you can use $rootScope.$broadcast if your $watch or $timeout fails

View

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

Controller

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

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

}]);

Directive

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

참고URL : https://stackoverflow.com/questions/12304291/angularjs-how-to-run-additional-code-after-angularjs-has-rendered-a-template

반응형