AngularJS 지시문에 정의 된 메소드를 호출하는 방법은 무엇입니까?


297

지시어가 있습니다. 코드는 다음과 같습니다.

.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {

            var center = new google.maps.LatLng(50.1, 14.4); 
            $scope.map_options = {
                zoom: 14,
                center: center,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            // create map
            var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
            var dirService= new google.maps.DirectionsService();
            var dirRenderer= new google.maps.DirectionsRenderer()

            var showDirections = function(dirResult, dirStatus) {
                if (dirStatus != google.maps.DirectionsStatus.OK) {
                    alert('Directions failed: ' + dirStatus);
                    return;
                  }
                  // Show directions
                dirRenderer.setMap(map);
                //$scope.dirRenderer.setPanel(Demo.dirContainer);
                dirRenderer.setDirections(dirResult);
            };

            // Watch
            var updateMap = function(){
                dirService.route($scope.dirRequest, showDirections); 
            };    
            $scope.$watch('dirRequest.origin', updateMap);

            google.maps.event.addListener(map, 'zoom_changed', function() {
                $scope.map_options.zoom = map.getZoom();
              });

            dirService.route($scope.dirRequest, showDirections);  
        }
    }
})

updateMap()사용자 조치 를 요청 하고 싶습니다 . 조치 단추가 지시문에 없습니다.

updateMap()컨트롤러에서 전화하는 가장 좋은 방법은 무엇입니까 ?


11
작은 참고 사항 : 스코프는 삽입되지 않고 일반 인수로 전달되므로 링크 함수에서 'scope'에 달러 기호를 사용하지 않는 것이 좋습니다.
Noam

답변:


369

분리 된 범위를 사용 =하려면 컨트롤러 범위에서 변수의 양방향 바인딩 을 사용하여 제어 개체를 전달할 수 있습니다 . 동일한 제어 오브젝트를 사용하여 페이지에서 동일한 지시문의 여러 인스턴스를 제어 할 수도 있습니다.

angular.module('directiveControlDemo', [])

.controller('MainCtrl', function($scope) {
  $scope.focusinControl = {};
})

.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{internalControl}}</div>',
    scope: {
      control: '='
    },
    link: function(scope, element, attrs) {
      scope.internalControl = scope.control || {};
      scope.internalControl.takenTablets = 0;
      scope.internalControl.takeTablet = function() {
        scope.internalControl.takenTablets += 1;
      }
    }
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
  <div ng-controller="MainCtrl">
    <button ng-click="focusinControl.takeTablet()">Call directive function</button>
    <p>
      <b>In controller scope:</b>
      {{focusinControl}}
    </p>
    <p>
      <b>In directive scope:</b>
      <focusin control="focusinControl"></focusin>
    </p>
    <p>
      <b>Without control object:</b>
      <focusin></focusin>
    </p>
  </div>
</div>


11
+1 이것은 Angular에서 재사용 가능한 컴포넌트에 대한 API를 작성하는 방법이기도합니다.
romiem

5
내가 대답하지 않은 경우 이것은 허용 답변보다 깨끗하고, 심슨 참조에 +1
Blake Miller

44
그것은 내가 똑같은 문제를 해결 한 방법입니다. 작동하지만 해킹처럼 보입니다 ... 각도에 더 나은 해결책이 있기를 바랍니다.
Dema

1
나는 각도를 배우고 있기 때문에 내 의견은별로 중요하지 않을 수 있지만이 접근법은 다른 대답보다 훨씬 직관적이며 올바른 대답으로 표시했을 것입니다. 나는 문제없이 제 샌드 박스 응용 프로그램에서 이것을 구현했습니다.
BLSully

4
당신은 아마 확인하기 위해 검사를해야한다 scope.control, 존재하는 지시어를 사용하지만이 지침의 방법에 액세스 할 필요가 없습니다 그리고이없는 그렇지 않으면 다른 장소 control에 집합 할 수 없다는 대한 오류 속성을 던지기 시작합니다 ATTR을undefined
CheapSteaks

73

동작 버튼 $scope이 지시어 와 동일한 컨트롤러 를 사용한다고 가정하면 링크 기능 내부에 기능 updateMap을 정의 $scope하십시오. 그런 다음 컨트롤러는 작업 버튼을 클릭하면 해당 기능을 호출 할 수 있습니다.

<div ng-controller="MyCtrl">
    <map></map>
    <button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {
            $scope.updateMap = function() {
                alert('inside updateMap()');
            }
        }
    }
});

fiddle


@FlorianF의 의견에 따라 지시문이 고립 된 범위를 사용하면 상황이 더 복잡합니다. 그것을 작동시키는 한 가지 방법이 있습니다 : 지시어 기능을 컨트롤러에 등록 할 지시어에 set-fn속성을 추가하십시오 map:

<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope: { setFn: '&' },
link: function(scope, element, attrs) {
    scope.updateMap = function() {
       alert('inside updateMap()');
    }
    scope.setFn({theDirFn: scope.updateMap});
}
function MyCtrl($scope) {
    $scope.setDirectiveFn = function(directiveFn) {
        $scope.directiveFn = directiveFn;
    };
}

fiddle


지시문에 격리 된 범위가 있으면 어떻게됩니까?
Florian F

감사! (어쩌면 지시어의 컨트롤러에 정의 된 함수를 호출하는 것이 더 쉬울 지 모르겠지만 확실하지 않습니다)
Florian F

1
격리 된 범위를 다루지 않는 경우이 방법이 훨씬 좋습니다.
Martin Frank

이 답변은 실제로 OP 질문에 답변합니다. 또한 격리 된 범위를 사용하기 위해 격리 된 범위 scope를 사용하므로 지시문 선언에 속성 을 추가하기 만하면됩니다.
Daniel G.

35

의사 소통의 격리 된 범위에서 개체와의 통신을 용이하게하려는 유혹이있을 수 있지만, 이렇게하면 "스파게티"코드가 혼동 될 수 있습니다. 특히이 통신을 몇 가지 수준 (제어기, 명령, 중첩 지시문 등)

우리는 원래이 길을 갔지만, 더 많은 연구 결과에 따르면, 지시어가 서비스를 통한 통신에 사용하고 그 서비스의 속성에 $ watch를 사용하여 이벤트와 속성을 노출하는 것이 더 이해하기 쉽고 코드를 유지하고 읽기 쉽다는 것을 발견했습니다. 의사 소통을 위해 이러한 변경 사항에 대응해야하는 지침 또는 기타 통제.

이 추상화는 AngularJS의 의존성 주입 프레임 워크와 매우 잘 작동하므로 해당 이벤트에 반응 해야하는 모든 항목에 서비스를 주입 할 수 있습니다. Angular.js 파일을 보면이 방식의 지시문도 이러한 방식으로 서비스와 $ watch를 사용한다는 것을 알 수 있으며 격리 된 범위에서 이벤트를 노출하지 않습니다.

마지막으로, 서로 종속 된 지시문간에 통신해야하는 경우, 통신 수단으로 해당 지시문간에 컨트롤러를 공유하는 것이 좋습니다.

AngularJS의 Wiki for Best Practices 도 다음과 같이 언급합니다.

원자 적 이벤트에는. $ broadcast (),. $ emit () 및. $ on () 만 사용하십시오. 전체 앱에서 전 세계적으로 관련된 이벤트 (예 : 사용자 인증 또는 앱 종료). 모듈, 서비스 또는 위젯 관련 이벤트를 원하는 경우 서비스, 지시문 컨트롤러 또는 타사 라이브러리를 고려해야합니다.

  • $ scope. $ watch ()는 이벤트의 필요성을 대체해야합니다
  • 서비스를 직접 주입하고 메소드를 호출하는 것도 직접적인 의사 소통에 유용합니다
  • 지시문은 지시문 컨트롤러를 통해 서로 직접 통신 할 수 있습니다.

2
나는 직관적으로 두 가지 해결책에 도달했습니다 : (1) 범위 변수의 변경을보고 =변수에는 메소드 이름과 인수가 포함되어 있습니다. (2) 단방향 바인딩 문자열 @을 주제 ID로 노출하고 수신자가이 주제에 대한 이벤트를 보내도록합니다. 이제 모범 사례 위키를 보았습니다. 나는 그렇게하지 않을 이유가 있다고 생각합니다. 그러나 나는 그것이 어떻게 작동하는지 여전히 명확하지 않습니다. 제 경우에는 탭셋 지시문을 만들었습니다 switchTab(tabIndex). 메소드 를 노출하고 싶습니다 . 더 예를 들어 주시겠습니까?
stanleyxu2005

switchTab(tabIndex)메소드를 공개하지 않고 tabIndex변수 에만 바인딩 합니다. 페이지 컨트롤러에 해당 변수를 변경하는 작업이있을 수 있습니다. 해당 변수를 탭 지시문에 바인딩 / 전달합니다. 그러면 탭 지시문에서 해당 변수의 변경 사항을 확인하고 자체 조정으로 switchTab을 수행 할 수 있습니다. 지시문은 변수를 기반으로 탭을 제어하는시기 / 방법을 결정하기 때문입니다. 그것은 외부 소스의 역할이 아닙니다. 그렇지 않으면 외부 소스는 지시문의 내부 작동에 대한 지식이 필요합니다.
Suamere

15

Oliver의 대답을 바탕으로-지시문의 내부 메소드에 항상 액세스 할 필요는 없으며, 이러한 경우 control오류가 발생하지 않도록 빈 객체를 작성 하고 지시문에 attr을 추가하지 않아도됩니다 ( cannot set property 'takeTablet' of undefined).

지시문 내의 다른 위치에서이 방법을 사용할 수도 있습니다.

scope.control존재 하는지 확인 하고 공개 모듈 패턴과 유사한 방식으로 메소드를 설정합니다.

app.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{control}}</div>',
    scope: {
      control: '='
    },
    link : function (scope, element, attrs) {
      var takenTablets = 0;
      var takeTablet = function() {
        takenTablets += 1;  
      }

      if (scope.control) {
        scope.control = {
          takeTablet: takeTablet
        };
      }
    }
  };
});

지시문에 공개 패턴을 ​​사용하면 의도가 훨씬 명확 해집니다. 좋은 것!
JSancho

12

솔직히 말해서, 나는이 스레드에서 어떤 대답도 확신하지 못했습니다. 그래서, 여기 내 해결책이 있습니다 :

지시문 처리기 (관리자) 접근

이 방법은 지시어 $scope가 공유 된 것인지 분리 된 것인지 아닌지에 관계없이

factory지시문 인스턴스를 등록하는 A

angular.module('myModule').factory('MyDirectiveHandler', function() {
    var instance_map = {};
    var service = {
        registerDirective: registerDirective,
        getDirective: getDirective,
        deregisterDirective: deregisterDirective
    };

    return service;

    function registerDirective(name, ctrl) {
        instance_map[name] = ctrl;
    }

    function getDirective(name) {
        return instance_map[name];
    }

    function deregisterDirective(name) {
        instance_map[name] = null;
    }
});

지시문 코드는 일반적으로 지시문 컨트롤러 내부에 DOM을 다루지 않는 모든 논리를 넣습니다. 그리고 핸들러 내부에 컨트롤러 인스턴스를 등록

angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {
    var directive = {
        link: link,
        controller: controller
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        var name = $attrs.name;

        this.updateMap = function() {
            //some code
        };

        MyDirectiveHandler.registerDirective(name, this);

        $scope.$on('destroy', function() {
            MyDirectiveHandler.deregisterDirective(name);
        });
    }
})

템플릿 코드

<div my-directive name="foo"></div>

factory공개적으로 노출 된 메소드를 실행하고 실행 하여 컨트롤러 인스턴스에 액세스

angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {
    $scope.someFn = function() {
        MyDirectiveHandler.get('foo').updateMap();
    };
});

각도의 접근

그들이 다루는 방법에 대한 앵귤러의 책에서 잎을 가져 오기

<form name="my_form"></form>

$ parse를 사용 하고 $parent범위에서 컨트롤러를 등록하십시오 . 이 기법은 분리 된 $scope지시문 에서는 작동하지 않습니다 .

angular.module('myModule').directive('myDirective', function($parse) {
    var directive = {
        link: link,
        controller: controller,
        scope: true
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        $parse($attrs.name).assign($scope.$parent, this);

        this.updateMap = function() {
            //some code
        };
    }
})

다음을 사용하여 컨트롤러 내부에 액세스하십시오. $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});

"Angular의 접근 방식"은 훌륭해 보입니다! 그래도 오타 $scope.foo가 있습니다.$scope.my_form
Daniel D

아니요, $scope.foo템플릿이 <div my-directive name="foo"></div>있고 name속성 값이 'foo' 이기 때문입니다 . <form이 기술을 사용하는 각도 지시어 중 하나의
예일뿐입니다

10

조금 늦었지만 이것은 지시문에서 함수를 호출하는 격리 된 범위와 "이벤트"가있는 솔루션입니다. 이 솔루션은 영감 이 SO 게시물 에 의해 satchmorun 과 모듈과 API를 추가합니다.

//Create module
var MapModule = angular.module('MapModule', []);

//Load dependency dynamically
angular.module('app').requires.push('MapModule');

지시문과 통신 할 API를 작성하십시오. addUpdateEvent는 이벤트를 이벤트 배열에 추가하고 updateMap은 모든 이벤트 함수를 호출합니다.

MapModule.factory('MapApi', function () {
    return {
        events: [],

        addUpdateEvent: function (func) {
            this.events.push(func);
        },

        updateMap: function () {
            this.events.forEach(function (func) {
                func.call();
            });
        }
    }
});

이벤트를 제거하려면 기능을 추가해야 할 수도 있습니다.

지시문에서 MapAPI에 대한 참조를 설정하고 MapApi.updateMap이 호출 될 때 $ scope.updateMap을 이벤트로 추가하십시오.

app.directive('map', function () {
    return {
        restrict: 'E', 
        scope: {}, 
        templateUrl: '....',
        controller: function ($scope, $http, $attrs, MapApi) {

            $scope.api = MapApi;

            $scope.updateMap = function () {
                //Update the map 
            };

            //Add event
            $scope.api.addUpdateEvent($scope.updateMap);

        }
    }
});

"main"컨트롤러에서 MapApi에 대한 참조를 추가하고 MapApi.updateMap ()을 호출하여 맵을 업데이트하십시오.

app.controller('mainController', function ($scope, MapApi) {

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}

2
이 제안은 API 서비스에 따라 동일한 유형의 지시문이 여러 개인 경우 실제 환경에서 약간 더 많은 작업이 필요합니다. 하나의 특정 지시문에서만 함수를 대상으로 지정하고 호출해야하는 상황이 발생합니다. 이에 대한 해결책으로 답변을 강화하고 싶습니까?
smajl

5

지시문이 상위 범위에서 함수를 정의하도록 허용하는 데 사용할 수있는 DOM 속성을 지정할 수 있습니다. 그러면 상위 범위는 다른 방법과 같이이 메소드를 호출 할 수 있습니다. 여기 plunker은. 아래는 관련 코드입니다.

clearfn 부모 범위가 범위 속성을 전달하여 지시문이 원하는 동작을 수행하는 함수로 설정할 수있는 지시문 요소의 속성입니다.

<!DOCTYPE html>
<html ng-app="myapp">
  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <style>
      my-box{
        display:block;
        border:solid 1px #aaa;
        min-width:50px;
        min-height:50px;
        padding:.5em;
        margin:1em;
        outline:0px;
        box-shadow:inset 0px 0px .4em #aaa;
      }
    </style>
  </head>
  <body ng-controller="mycontroller">
    <h1>Call method on directive</h1>
    <button ng-click="clear()">Clear</button>
    <my-box clearfn="clear" contentEditable=true></my-box>
    <script>
      var app = angular.module('myapp', []);
      app.controller('mycontroller', function($scope){
      });
      app.directive('myBox', function(){
        return {
          restrict: 'E',
          scope: {
            clearFn: '=clearfn'
          },
          template: '',
          link: function(scope, element, attrs){
            element.html('Hello World!');
            scope.clearFn = function(){
              element.html('');
            };
          }
        }
      });
    </script>
  </body>
</html>

왜 이것이 작동하는지 이해하지 못합니다. 명확한 속성의 범위가 어느 정도입니까?
Quinn Wilson

1
선언하자마자 지시문 범위의 일부가됩니다 (예 :) scope: { clearFn: '=clearfn' }.
Trevor

2

scope. $ parent를 사용하여 호출 된 함수를 지시문 함수에 연결하십시오.

angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) {

}])
.directive('mydirective',function(){
 function link(scope, el, attr){
   //use scope.$parent to associate the function called to directive function
   scope.$parent.myfunction = function directivefunction(parameter){
     //do something
}
}
return {
        link: link,
        restrict: 'E'   
      };
});

HTML로

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>

2

메소드 이름에 지시문에 지시하여 격리 범위없이 컨트롤러에서 호출 할 대상을 정의 할 수 있습니다.

angular.module("app", [])
  .directive("palyer", [
    function() {
      return {
        restrict: "A",
        template:'<div class="player"><span ng-bind="text"></span></div>',
        link: function($scope, element, attr) {
          if (attr.toPlay) {
            $scope[attr.toPlay] = function(name) {
              $scope.text = name + " playing...";
            }
          }
        }
      };
    }
  ])
  .controller("playerController", ["$scope",
    function($scope) {
      $scope.clickPlay = function() {
        $scope.play('AR Song');
      };
    }
  ]);
.player{
  border:1px solid;
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="playerController">
    <p>Click play button to play
      <p>
        <p palyer="" to-play="play"></p>
        <button ng-click="clickPlay()">Play</button>

  </div>
</div>


1

TESTED 누군가에게 도움이 되기를 바랍니다 .

내 간단한 접근 방식 (태그를 원래 코드로 생각하십시오)

<html>
<div ng-click="myfuncion"> 
<my-dir callfunction="myfunction">
</html>

<directive "my-dir">
callfunction:"=callfunction"
link : function(scope,element,attr) {
scope.callfunction = function() {
 /// your code
}
}
</directive>

0

어쩌면 이것이 최선의 선택은 아니지만 지시문의 범위 및 / 또는 컨트롤러에 액세스 angular.element("#element").isolateScope()하거나 $("#element").isolateScope()액세스 할 수 있습니다 .


0

페이지 컨트롤러에서 지시문 컨트롤러를 얻는 방법 :

  1. DOM 요소에서 지시문 컨트롤러에 대한 참조를 얻으려면 사용자 지정 지시문을 작성하십시오.

    angular.module('myApp')
        .directive('controller', controller);
    
    controller.$inject = ['$parse'];
    
    function controller($parse) {
        var directive = {
            restrict: 'A',
            link: linkFunction
        };
        return directive;
    
        function linkFunction(scope, el, attrs) {
            var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
            var directiveController = el.controller(directiveName);
    
            var model = $parse(attrs.controller);
            model.assign(scope, directiveController);
        }
    }
  2. 페이지 컨트롤러의 html에서 사용하십시오.

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. 페이지 컨트롤러에서 지시문 컨트롤러를 사용하십시오.

    vm.myDirectiveController.callSomeMethod();

참고 : 주어진 솔루션은 요소 지시문의 컨트롤러에만 작동합니다 (태그 이름은 원하는 지시문의 이름을 얻는 데 사용됩니다).


0

아래 솔루션은 'Controller As'형식의 컨트롤러 (부모 및 지시문 (모두))가있는 경우 유용합니다

누군가는 이것이 유용하다는 것을 알 수 있습니다.

지시문 :

var directive = {
        link: link,
        restrict: 'E',
        replace: true,
        scope: {
            clearFilters: '='
        },
        templateUrl: "/temp.html",
        bindToController: true, 
        controller: ProjectCustomAttributesController,
        controllerAs: 'vmd'
    };
    return directive;

    function link(scope, element, attrs) {
        scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
    }
}

지시어 컨트롤러 :

function DirectiveController($location, dbConnection, uiUtility) {
  vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;

function SetFitlersToDefaultValue() {
           //your logic
        }
}

html 코드 :

      <Test-directive clear-filters="vm.ClearFilters"></Test-directive>
    <a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a> 
//this button is from parent controller which will call directive controller function
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.