AngularJS yönergesinde tanımlanan bir yöntemi nasıl çağırırım?


298

Bir direktif var, işte kod:

.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()Bir kullanıcı eylemini çağırmak istiyorum . Eylem düğmesi yönergede değil.

updateMap()Bir denetleyiciden arama yapmanın en iyi yolu nedir ?


11
Küçük yan not: Kural, kapsamın enjekte edilmediği, ancak normal bir argüman olarak iletildiği için, dolar işlevini bağlantı işlevinde 'kapsam' için kullanmak değildir.
Noam

Yanıtlar:


369

Yalıtılmış kapsamlar kullanmak istiyorsanız =, denetleyicinin kapsamından bir değişkenin iki yönlü bağlamasını kullanarak bir kontrol nesnesini iletebilirsiniz . Ayrıca, aynı denetim nesnesine sahip bir sayfada aynı yönergelerin birkaç örneğini de denetleyebilirsiniz.

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 Bu, Angular'daki yeniden kullanılabilir bileşenlerim için API'leri nasıl oluşturduğumdur.
romiem

5
Bu, kabul edilen cevaptan daha temiz ve eğer yanılmıyorsam, simpsons referansı için +1
Blake Miller

44
Aynı problemi tam olarak bu şekilde çözdüm. İşe yarıyor, ama bir kesmek gibi görünüyor ... Keşke açısal bunun için daha iyi bir çözüm olsaydı.
Dema

1
Açısal öğreniyorum, bu yüzden fikrim çok fazla ağırlık taşımayabilir, ancak bu yaklaşımı diğer cevaptan çok daha sezgisel buldum ve doğru cevap olarak işaretlerdim. Bunu sıfır sorunla sandbox uygulamamda uyguladım.
BLSully

4
Muhtemelen scope.controlvar olduğundan emin olmak için bir kontrol yapmalısınız , aksi takdirde direktifi kullanan ancak direktifin yöntemlerine erişmesi gerekmeyen ve bir controlundefined
attr'e sahip olmayan diğer

73

Eylem düğmesi aynı denetleyicisi kullanır varsayarsak $scopeyönergesi gibi, sadece işlevini tanımlamak updateMapüzerinde $scopebağlantı işlevi içinde. Daha sonra kontrol düğmeniz eylem düğmesi tıklandığında bu işlevi çağırabilir.

<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'ın yorumuna göre, direktif yalıtılmış bir kapsam kullanıyorsa, işler daha karmaşıktır. Çalışmasını sağlamanın bir yolu şudur: Direktife, yönerge işlevini denetleyiciye kaydedecek bir set-fnöznitelik ekleyin 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


Direktifin yalıtılmış bir kapsamı varsa ne olur?
Florian F

Teşekkürler! (Belki de direktifin kontrolöründe tanımlanan bir işlevi çağırmak daha kolay olurdu ama bundan emin değilim)
Florian F

1
Yalıtılmış bir kapsamla ilgilenmiyorsanız, bu çok daha iyi bir yoldur.
Martin Frank

Bu cevap aslında OP sorusunu cevaplıyor. Ayrıca, yalıtılmış kapsamı da kullanır, yalıtılmış bir kapsama sahip olmak için scopeözelliği yalnızca yönerge beyanına eklemeniz gerekir .
Daniel G.

35

Bir nesneyi onunla iletişim kurmayı kolaylaştırmak için direktifin yalıtılmış kapsamına maruz bırakmak cazip gelse de, özellikle bu iletişimi birkaç seviyeden (kontrolör, direktifle zincirlemeniz gerekiyorsa) karıştırmak "spagetti" koduna yol açabilir. iç içe yönerge vb.)

Başlangıçta bu yoldan gittik, ancak daha fazla araştırma yapmanın daha mantıklı olduğunu ve bir direktifin bir hizmet yoluyla iletişim için kullanacağı olayları ve özellikleri açığa çıkarmak için hem daha sürdürülebilir hem de okunabilir bir kodla sonuçlandıktan sonra o hizmetin özelliklerinde $ watch kullanarak direktif veya iletişim için bu değişikliklere tepki vermesi gereken diğer kontroller.

Bu soyutlama, AngularJS'nin bağımlılık enjeksiyon çerçevesiyle çok iyi çalışır, çünkü hizmeti bu olaylara tepki vermesi gereken herhangi bir öğeye enjekte edebilirsiniz. Angular.js dosyasına bakarsanız, oradaki direktiflerin hizmetleri ve $ watch'u da bu şekilde kullandığını görürsünüz, olayları yalıtılmış kapsam üzerinde göstermezler.

Son olarak, birbirine bağımlı direktifler arasında iletişim kurmanız gerektiğinde, iletişim aracı olarak bu direktifler arasında bir kontrolörün paylaşılmasını tavsiye ederim.

AngularJS'nin En İyi Uygulamalar için Wiki'si de bundan bahsediyor:

Atom olayları için yalnızca. $ Broadcast (),. $ Emit () ve. $ On () yöntemlerini tüm uygulama genelinde global olarak alakalı olan etkinlikler (kullanıcı kimlik doğrulaması veya uygulama kapanışı gibi) kullanın. Modüllere, hizmetlere veya widget'lara özgü etkinlikler istiyorsanız Hizmetler, Direktif Denetleyicileri veya 3. Taraf Lib'leri göz önünde bulundurmalısınız

  • $ scope. $ watch () etkinlik ihtiyacını değiştirmelidir
  • Doğrudan enjeksiyon için hizmetler ve çağrı yöntemleri doğrudan iletişim için de yararlıdır
  • Direktifler direktif kontrolörleri aracılığıyla birbirleriyle doğrudan iletişim kurabilirler

2
Sezgisel olarak iki çözüme ulaştım: (1) kapsam değişkeninin değişimini izleyin =, değişken yöntem adı ve argümanlar içerir. (2) @konu kimliği olarak tek yönlü bir dize göstermek ve callee bu konuda olay göndermek izin. Şimdi en iyi uygulama wiki'sini gördüm. Bence bunu yapmamak için bir sebep var. Ama hala çok net değilim, nasıl çalıştığını. Benim durumumda, bir tabset yönergesi oluşturdum, bir switchTab(tabIndex)yöntemi ortaya çıkarmak istiyorum . Daha fazla örnek verebilir misiniz?
stanleyxu2005

Bir switchTab(tabIndex)yöntemi göstermezsiniz, sadece bir tabIndexdeğişkene bağlanırsınız . Sayfa denetleyicinizde bu değişkeni değiştiren eylemler olabilir. Bu değişkeni sekme Direktifinize bağlar / iletirsiniz. Sekme Direktifiniz daha sonra bu değişkeni değişiklikler için izleyebilir ve kendi isteğine göre switchTab uygulayabilir. Çünkü direktif sekmelerini bir değişkene göre ne zaman / nasıl kontrol edeceğine karar verir. Bu harici bir kaynağın işi değildir, aksi halde harici kaynaklar direktifin içsel işleyişini bilmeyi gerektirir, ki bu kötüdür.
Suamere

15

Oliver'ın cevabına dayanarak - her zaman bir direktifin iç yöntemlerine erişmeniz gerekmeyebilir ve bu durumlarda muhtemelen boş bir nesne oluşturmak controlve direktifin bir hata atmasını önlemek için bir yıpranma eklemek istemezsiniz ( cannot set property 'takeTablet' of undefined).

Yöntemi direktifin diğer yerlerinde de kullanmak isteyebilirsiniz.

Var olduğundan emin olmak için bir kontrol eklerim scope.controlve açıklayıcı modül modeline benzer şekilde yöntemler ayarlarım

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

noktaya gelince, direktifin içinde açıklayıcı bir desen kullanmak niyetleri çok daha net hale getirir. güzel bir!
JSancho

12

Dürüst olmak gerekirse, bu konudaki cevapların hiçbiriyle gerçekten ikna olmadım. İşte benim çözümlerim:

Direktif İşleyici (Yönetici) Yaklaşımı

Bu yöntem, direktifin $scopepaylaşılan veya izole bir yöntem olup olmadığına agnostiktir

factoryDirektif örneklerini kaydetmek için 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;
    }
});

Direktif kodu, genellikle DOM ile ilgili olmayan tüm mantığı direktif kontrolörü içine koydum. Denetleyici örneğini işleyicimize kaydetme

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

şablon kodu

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

Genel olarak karşılaşılan factoryyöntemleri kullanarak & çalıştır komutunu kullanarak denetleyici örneğine erişin

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

Açısal yaklaşım

Angular'ın nasıl başa çıktıklarına dair kitabından bir yaprak çıkarmak

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

kapsamda $ ayrıştırma ve kayıt denetleyicisi kullanma $parent. Bu teknik yalıtılmış $scopeyönergeler üzerinde çalışmaz .

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

Kullanarak denetleyicinin içine erişin $scope.foo

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

"Angular'ın yaklaşımı" harika görünüyor! Yine de bir yazım hatası var: $scope.fooolmalı$scope.my_form
Daniel D

Hayır, $scope.fooçünkü şablonumuz <div my-directive name="foo"></div>ve nameözniteliğin değeri 'foo'. <formbu tekniği kullanan açısal direktiflerden sadece bir örneğidir
Mudassir Ali

10

Biraz geç, ama bu yönerge bir işlevi çağırmak için izole kapsam ve "olaylar" ile bir çözümdür. Bu çözelti esinlenerek , bu yüzden post ile satchmorun bir modülü ve bir API ekler.

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

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

Yönerge ile iletişim kurmak için bir API oluşturun. AddUpdateEvent, olay dizisine bir olay ekler ve updateMap her olay işlevini çağırır.

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

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

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

(Belki etkinliği kaldırmak için işlevsellik eklemeniz gerekir.)

Yönerge'de MapAPI'ye bir başvuru ayarlayın ve MapApi.updateMap çağrıldığında $ scope.updateMap öğesini bir olay olarak ekleyin.

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

        }
    }
});

"Ana" denetleyicide MapApi'ye bir başvuru ekleyin ve haritayı güncellemek için MapApi.updateMap () öğesini çağırmanız yeterlidir.

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

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

2
API teklifinize bağlı olarak aynı türde birden fazla direktifiniz olduğunda, bu teklifin gerçek dünyada biraz daha çalışması gerekir. Tüm işlevleri değil, yalnızca belirli bir yönergeden işlevleri hedeflemeniz ve çağırmanız gereken bir duruma gireceksiniz. Buna bir çözüm ile cevabınızı geliştirmek ister misiniz?
smajl

5

Direktifin üst kapsamda bir işlev tanımlamasına izin vermek için kullanılabilecek bir DOM özelliği belirtebilirsiniz. Üst kapsam daha sonra bu yöntemi diğerleri gibi çağırabilir. İşte bir dalgıç. Ve aşağıda ilgili kod.

clearfn yönerge öğesinde, üst kapsamın, yönerge istenen davranışı gerçekleştiren bir işleve ayarlanabileceği bir kapsam özelliğini geçebileceği bir özniteliktir.

<!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>

Bunun neden işe yaradığını anlamıyorum .. çünkü net özniteliğin kapsamı nasıl?
Quinn Wilson

1
Bildirir bildirmez direktifin kapsamının bir parçası haline gelir (örn. scope: { clearFn: '=clearfn' }).
Trevor

2

Yönerge işlevine çağrılan işlevi ilişkilendirmek için kapsam. $ Parent kullanmanız yeterlidir

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'de

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

2

İzleyici kapsamı olmayan, ancak denetleyiciden aramak istediğinizi tanımlamak için yöntem adını yönerge olarak söyleyebilirsiniz,

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

TEST EDİLDİ Umarım bu birine yardımcı olur.

Basit yaklaşımım (Etiketleri orijinal kodunuz olarak düşünün)

<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

Belki de bu en iyi seçim değildir, ancak direktifinizin kapsamına ve / veya denetleyicisine erişebilir angular.element("#element").isolateScope()veya $("#element").isolateScope()erişebilirsiniz.


0

Bir yönerge denetleyicisini sayfa denetleyicisine alma:

  1. DOM öğesinden yönerge denetleyicisine referans almak için özel bir yönerge yazın:

    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. sayfa denetleyicisinin html'sinde kullanın:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. Sayfa denetleyicisindeki yönerge denetleyicisini kullanın:

    vm.myDirectiveController.callSomeMethod();

Not: Verilen çözüm yalnızca eleman direktiflerinin kontrolörleri için çalışır (etiket adı, istenen direktifin adını almak için kullanılır).


0

Aşağıdaki çözüm, denetleyici (hem üst hem de yönerge (yalıtılmış)) 'denetleyici Farklı' biçiminde olduğunda yararlı olacaktır

birisi bunu faydalı bulabilir,

yönerge:

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

yönergesi Denetleyici:

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

function SetFitlersToDefaultValue() {
           //your logic
        }
}

HTML Kodu :

      <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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.