AngularJS에서 지시문에서 지시문 추가


197

선언 된 요소에 지시문더 추가 하는 지시문을 작성하려고합니다 . 예를 들어 datepicker, datepicker-language및 을 추가하는 지시문을 작성하려고합니다 ng-required="true".

해당 속성을 추가하고 사용하려고하면 $compile분명히 무한 루프를 생성하므로 필요한 속성을 이미 추가했는지 확인하고 있습니다.

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        element.attr('datepicker', 'someValue');
        element.attr('datepicker-language', 'en');
        // some more
        $compile(element)(scope);
      }
    };
  });

물론 $compile요소 가 없으면 속성이 설정되지만 지시문은 부트 스트랩되지 않습니다.

이 접근법이 맞습니까? 아니면 잘못하고 있습니까? 같은 행동을하는 더 좋은 방법이 있습니까?

UDPATE : $compile이것을 달성하는 유일한 방법 이라는 사실을 감안할 때 첫 번째 컴파일 패스를 건너 뛸 수있는 방법이 있습니까 (요소에는 여러 자식이 포함될 수 있음)? 어쩌면 terminal:true?

업데이트 2 : 지시어를 select요소에 넣으려고했는데 예상대로 컴파일이 두 번 실행됩니다. 즉, 예상 횟수가 두 배 option입니다.

답변:


260

단일 DOM 요소에 여러 지시문이 있고 적용되는 순서가 중요한 경우이 priority속성을 사용하여 응용 프로그램을 주문할 수 있습니다 . 높은 숫자가 먼저 실행됩니다. 지정하지 않으면 기본 우선 순위는 0입니다.

편집 : 토론 후 완벽한 작업 솔루션이 있습니다. 열쇠는 것이었다 속성을 제거 : element.removeAttr("common-things");또한, 및 element.removeAttr("data-common-things");(경우에 사용자 지정 data-common-thingshtml로에서)

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false, 
      terminal: true, //this setting is important, see explanation below
      priority: 1000, //this setting is important, see explanation below
      compile: function compile(element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {  },
          post: function postLink(scope, iElement, iAttrs, controller) {  
            $compile(iElement)(scope);
          }
        };
      }
    };
  });

작업 플 런커는 http://plnkr.co/edit/Q13bUt?p=preview에 있습니다.

또는:

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false,
      terminal: true,
      priority: 1000,
      link: function link(scope,element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        $compile(element)(scope);
      }
    };
  });

데모

우리가 설정해야 할 이유를 설명 terminal: true하고 priority: 1000(높은 숫자) :

DOM이 준비되면 angular는 DOM을 따라 등록 된 모든 지시문을 식별하고 priority 이러한 지시문이 동일한 요소있는지 여부에 따라 지시문을 하나씩 컴파일합니다 . 우리는 컴파일 될 수 있도록하기 위해 높은 숫자로 우리의 사용자 정의 지침의 우선 순위를 설정 첫번째 와 함께 terminal: true, 다른 지침이 될 것이다 생략 이 지시어는 컴파일 후.

사용자 지정 지시문이 컴파일되면 지시문을 추가하고 자체를 제거하여 요소를 수정하고 $ compile 서비스를 사용하여 모든 지시문 (건너 뛴 지시문 포함)컴파일합니다 .

우리가 설정하지 않은 경우 terminal:truepriority: 1000, 어떤 지시어를 컴파일하는 기회가 되기 전에 우리의 사용자 정의 지시어. 그리고 우리의 커스텀 지시어가 $ compile을 사용하여 요소를 컴파일 할 때 => 이미 컴파일 된 지시문을 다시 컴파일하십시오. 이는 특히 사용자 지정 지시문 전에 컴파일 된 지시문이 이미 DOM을 변환 한 경우 예측할 수없는 동작을 유발합니다.

우선 순위 및 터미널에 대한 자세한 정보 는 지시문의 '터미널'을 이해하는 방법을 참조 하십시오 .

템플릿을 수정하는 지시문의 예 는 컴파일 ng-repeat다른 지시문이 적용되기 전에 템플리트 요소의 사본을 작성하는 (priority = 1000) 입니다.ng-repeatng-repeat

@Izhaki의 의견 덕분에 다음은 ngRepeat소스 코드에 대한 참조입니다 . https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js


5
RangeError: Maximum call stack size exceeded영원히 컴파일 될 때 스택 오버플로 예외가 발생합니다 .
frapontillo

3
@frapontillo : 귀하의 경우 element.removeAttr("common-datepicker");무한 루프를 피하기 위해 추가하십시오 .
Khanh TO

4
좋아, 내가 그것을 분류 할 수있었습니다, 당신은 설정해야합니다 replace: false, terminal: true, priority: 1000, 그런 다음 compile함수 에서 원하는 속성을 설정 하고 지시문 속성을 제거하십시오. 마지막으로에 post의해 반환 된 함수 compile에서를 호출 $compile(element)(scope)합니다. 요소는 사용자 지정 지시문없이 추가 된 속성으로 정기적으로 컴파일됩니다. 내가 달성하려는 것은 사용자 지정 지시문을 제거하지 않고 하나의 프로세스 에서이 모든 것을 처리하는 것이 었습니다.이 작업을 수행 할 수는 없습니다. 업데이트 된 plnkr : plnkr.co/edit/Q13bUt?p=preview를 참조하십시오 .
frapontillo

2
컴파일 또는 링크 함수의 attributes 오브젝트 매개 변수를 사용해야하는 경우 속성 값 보간을 담당하는 지시문의 우선 순위가 100이고 지시문의 우선 순위가 이보다 낮아야합니다. 그렇지 않으면 디렉토리가 터미널이기 때문에 속성의 문자열 값. 참조 ( 이 github 풀 요청 및 이와 관련된 문제 참조 )
Simen Echholt

2
common-things속성 을 제거하는 대신 maxPriority 매개 변수를 컴파일 명령에 전달할 수 있습니다.$compile(element, null, 1000)(scope);
Andreas

10

간단한 템플릿 태그만으로이 모든 것을 실제로 처리 할 수 ​​있습니다. 예는 http://jsfiddle.net/m4ve9/ 를 참조하십시오 . 실제로 슈퍼 지시문 정의에는 컴파일 또는 링크 속성이 필요하지 않습니다.

컴파일 과정에서 Angular는 컴파일하기 전에 템플릿 값을 가져 오므로 추가 지시문을 첨부 할 수 있으며 Angular가이를 처리합니다.

이것이 원래 내부 컨텐츠를 보존해야하는 수퍼 지시어 인 경우 내부를 사용 transclude : true하고 대체 할 수 있습니다.<ng-transclude></ng-transclude>

도움이 되길 바랍니다. 불분명 한 것이 있으면 알려주세요.

알렉스


Alex에게 감사합니다.이 접근법의 문제점은 태그가 무엇인지에 대한 가정을 할 수 없다는 것입니다. 이 예에서는 날짜 선택기, 즉 input태그이지만 divs 또는 selects 와 같은 모든 요소에서 작동하도록하고 싶습니다 .
frapontillo

1
아, 그래요 이 경우 div를 고수하고 다른 지시문이 작동하도록하는 것이 좋습니다. 가장 깨끗한 답변은 아니지만 Angular 방법론에 가장 적합합니다. 부트 스트랩 프로세스가 HTML 노드를 컴파일하기 시작할 때, 이미 컴파일 할 노드의 모든 지시문을 수집하므로 새 부트 스트랩 프로세스에 새 지시문을 추가하지 않아도됩니다. 필요에 따라 div의 모든 것을 감싸고 그 안에서 작업하면 유연성이 높아지지만 요소를 넣을 수있는 위치가 제한됩니다.
mrvdot

3
@frapontillo 템플릿을 함수로 사용 element하고 attrs전달할 수 있습니다. 나이를 먹어 그 기능을 발휘해 보았지만 어느 곳에서나 사용하지는 않았지만 제대로 작동하는 것 같습니다 : stackoverflow.com/a/20137542/1455709
Patrick

6

다음은 동적으로 추가해야하는 지시문을보기로 이동하고 선택적 (기본) 조건부 논리를 추가하는 솔루션입니다. 이것은 하드 코딩 된 로직없이 지시문을 깨끗하게 유지합니다.

지시문은 객체의 배열을 취합니다. 각 객체에는 추가 할 지시문의 이름과 전달할 값 (있는 경우)이 포함됩니다.

조건에 따라 지시문을 추가하는 조건부 논리를 추가하는 것이 유용 할 것이라고 생각할 때까지 지시문에 대한 유스 케이스를 생각하는 데 어려움을 겪고있었습니다 (아래 답변은 여전히 ​​유의됩니다). if지시어 추가 여부를 결정하는 부울 값, 표현식 또는 함수 (예 : 컨트롤러에 정의)가 포함되어야 하는 선택적 속성을 추가했습니다 .

또한 사용하고 attrs.$attr.dynamicDirectives지시어를 (예를 들어 추가하는 데 사용되는 정확한 속성 선언 얻기 위해 data-dynamic-directive, dynamic-directive확인하기 위해 하드 코딩 문자열 값없이)를.

Plunker Demo

angular.module('plunker', ['ui.bootstrap'])
    .controller('DatepickerDemoCtrl', ['$scope',
        function($scope) {
            $scope.dt = function() {
                return new Date();
            };
            $scope.selects = [1, 2, 3, 4];
            $scope.el = 2;

            // For use with our dynamic-directive
            $scope.selectIsRequired = true;
            $scope.addTooltip = function() {
                return true;
            };
        }
    ])
    .directive('dynamicDirectives', ['$compile',
        function($compile) {
            
             var addDirectiveToElement = function(scope, element, dir) {
                var propName;
                if (dir.if) {
                    propName = Object.keys(dir)[1];
                    var addDirective = scope.$eval(dir.if);
                    if (addDirective) {
                        element.attr(propName, dir[propName]);
                    }
                } else { // No condition, just add directive
                    propName = Object.keys(dir)[0];
                    element.attr(propName, dir[propName]);
                }
            };
            
            var linker = function(scope, element, attrs) {
                var directives = scope.$eval(attrs.dynamicDirectives);
        
                if (!directives || !angular.isArray(directives)) {
                    return $compile(element)(scope);
                }
               
                // Add all directives in the array
                angular.forEach(directives, function(dir){
                    addDirectiveToElement(scope, element, dir);
                });
                
                // Remove attribute used to add this directive
                element.removeAttr(attrs.$attr.dynamicDirectives);
                // Compile element to run other directives
                $compile(element)(scope);
            };
        
            return {
                priority: 1001, // Run before other directives e.g.  ng-repeat
                terminal: true, // Stop other directives running
                link: linker
            };
        }
    ]);
<!doctype html>
<html ng-app="plunker">

<head>
    <script src="//code.angularjs.org/1.2.20/angular.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
    <script src="example.js"></script>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>

<body>

    <div data-ng-controller="DatepickerDemoCtrl">

        <select data-ng-options="s for s in selects" data-ng-model="el" 
            data-dynamic-directives="[
                { 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' },
                { 'tooltip-placement' : 'bottom' },
                { 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' }
            ]">
            <option value=""></option>
        </select>

    </div>
</body>

</html>


다른 지시문 템플릿에서 사용됩니다. 잘 작동하고 내 시간을 절약하십시오. 고마워
jcstritt

4

수락 된 솔루션이 저에게 효과적이지 않기 때문에 솔루션을 추가하고 싶었습니다.

지시어를 추가해야했지만 요소를 유지해야했습니다.

이 예제에서는 간단한 ng 스타일 지시문을 요소에 추가합니다. 무한 컴파일 루프를 방지하고 지시문을 유지할 수 있도록 요소를 다시 컴파일하기 전에 추가 한 항목이 있는지 확인하는 검사를 추가했습니다.

angular.module('some.directive', [])
.directive('someDirective', ['$compile',function($compile){
    return {
        priority: 1001,
        controller: ['$scope', '$element', '$attrs', '$transclude' ,function($scope, $element, $attrs, $transclude) {

            // controller code here

        }],
        compile: function(element, attributes){
            var compile = false;

            //check to see if the target directive was already added
            if(!element.attr('ng-style')){
                //add the target directive
                element.attr('ng-style', "{'width':'200px'}");
                compile = true;
            }
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {  },
                post: function postLink(scope, iElement, iAttrs, controller) {
                    if(compile){
                        $compile(iElement)(scope);
                    }
                }
            };
        }
    };
}]);

컴파일러가 두 번째 라운드에서 다시 적용하려고 시도하므로 transclude 또는 템플리트와 함께이를 사용할 수 없습니다.
spikyjt

1

다음과 같은 요소 자체의 속성에 상태를 저장하십시오. superDirectiveStatus="true"

예를 들면 다음과 같습니다.

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        var status = element.attr('superDirectiveStatus');
        if( status !== "true" ){
             element.attr('datepicker', 'someValue');
             element.attr('datepicker-language', 'en');
             // some more
             element.attr('superDirectiveStatus','true');
             $compile(element)(scope);

        }

      }
    };
  });

이것이 도움이되기를 바랍니다.


고마워, 기본 개념은 동일하게 유지됩니다 :). 첫 번째 컴파일 패스를 건너 뛸 수있는 방법을 찾으려고합니다. 원래 질문을 업데이트했습니다.
frapontillo

이중 컴파일은 끔찍한 방식으로 문제를 해결합니다.
frapontillo

1

1.3.x에서 1.4.x로 변경되었습니다.

Angular 1.3.x에서는 다음과 같이 작동했습니다.

var dir: ng.IDirective = {
    restrict: "A",
    require: ["select", "ngModel"],
    compile: compile,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
        attributes["ngOptions"] = "a.ID as a.Bezeichnung for a in akademischetitel";
        scope.akademischetitel = AkademischerTitel.query();
    }
}

이제 Angular 1.4.x에서는 다음을 수행해야합니다.

var dir: ng.IDirective = {
    restrict: "A",
    compile: compile,
    terminal: true,
    priority: 10,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");
    tElement.removeAttr("tq-akademischer-titel-select");
    tElement.attr("ng-options", "a.ID as a.Bezeichnung for a in akademischetitel");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {

        $compile(element)(scope);
        scope.akademischetitel = AkademischerTitel.query();
    }
}

(허용 된 답변에서 : Khanh TO의 https://stackoverflow.com/a/19228302/605586 ).


0

경우에 따라 작동 할 수있는 간단한 솔루션은 래퍼를 만들어 $ 컴파일 한 다음 원래 요소를 추가하는 것입니다.

뭔가 ...

link: function(scope, elem, attr){
    var wrapper = angular.element('<div tooltip></div>');
    elem.before(wrapper);
    $compile(wrapper)(scope);
    wrapper.append(elem);
}

이 솔루션은 원래 요소를 다시 컴파일하지 않아도 작업을 단순하게 유지하는 이점이 있습니다.

추가 된 지시문 require중 하나라도 원래 요소의 지시문 중 하나이거나 원래 요소에 절대 위치가 있으면 작동하지 않습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.