Bagaimana cara memanggil metode yang didefinisikan dalam arahan AngularJS?

297

Saya punya arahan, ini kode:

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

Saya ingin updateMap()melakukan aksi pengguna. Tombol tindakan tidak ada pada arahan.

Apa cara terbaik untuk memanggil updateMap()dari pengontrol?

mcbjam
sumber
11
Catatan kecil: konvensi ini tidak menggunakan tanda dolar untuk 'lingkup' dalam fungsi tautan, karena ruang lingkup tidak disuntikkan tetapi diteruskan sebagai argumen biasa.
Noam

Jawaban:

369

Jika Anda ingin menggunakan cakupan terisolasi, Anda bisa melewatkan objek kontrol menggunakan pengikatan dua arah =variabel dari lingkup pengontrol. Anda juga dapat mengontrol beberapa contoh direktif yang sama pada halaman dengan objek kontrol yang sama.

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>

Oliver Wienand
sumber
11
+1 Ini juga cara saya membuat API untuk komponen yang dapat digunakan kembali di Angular.
romiem
5
Ini lebih bersih daripada jawaban yang diterima, dan +1 untuk referensi simpsons, jika saya tidak salah
Blake Miller
44
Begitulah cara saya memecahkan masalah yang sama. Ini berfungsi, tetapi sepertinya retasan ... Saya berharap sudut memiliki solusi yang lebih baik untuk ini.
Dema
1
Saya belajar bersudut, jadi pendapat saya mungkin tidak terlalu berpengaruh, tetapi saya menemukan pendekatan ini jauh lebih intuitif daripada jawaban yang lain dan akan menandainya sebagai jawaban yang benar. Saya menerapkan ini dalam aplikasi kotak pasir saya tanpa kesulitan.
BLSully
4
Anda mungkin harus melakukan pemeriksaan untuk memastikan scope.controlada, jika tidak tempat lain yang menggunakan arahan tetapi tidak perlu mengakses metode arahan dan tidak memiliki controlattr akan mulai melempar kesalahan tentang tidak dapat mengatur atribut padaundefined
CheapSteaks
73

Dengan asumsi bahwa tombol tindakan menggunakan pengontrol yang sama $scopedengan arahan, cukup tentukan fungsi updateMapdi $scopedalam fungsi tautan. Pengontrol Anda kemudian dapat memanggil fungsi itu ketika tombol aksi diklik.

<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


Sesuai komentar @ FlorianF, jika direktif menggunakan ruang lingkup yang terisolasi, segalanya menjadi lebih rumit. Inilah salah satu cara untuk membuatnya bekerja: tambahkan set-fnatribut ke mapdirektif yang akan mendaftarkan fungsi direktif dengan controller:

<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

Mark Rajcok
sumber
Bagaimana jika direktif memiliki ruang lingkup yang terisolasi?
Florian F
Terima kasih! (Mungkin akan lebih mudah untuk memanggil fungsi yang didefinisikan dalam pengontrol direktif tetapi saya tidak yakin tentang hal itu)
Florian F
1
Ini jauh lebih baik jika Anda tidak berurusan dengan ruang lingkup yang terisolasi.
Martin Frank
Jawaban ini benar-benar menjawab pertanyaan OP. Itu juga menggunakan lingkup terisolasi, untuk memiliki ruang lingkup terisolasi Anda hanya perlu menambahkan scopeproperti ke dalam deklarasi direktif.
Daniel G.
35

Meskipun mungkin tergoda untuk mengekspos objek pada lingkup terisolasi dari direktif untuk memfasilitasi berkomunikasi dengannya, melakukan dapat menyebabkan kode "spaghetti" membingungkan, terutama jika Anda perlu untuk rantai komunikasi ini melalui beberapa level (pengontrol, untuk mengarahkan, ke arahan bersarang, dll.)

Kami awalnya menempuh jalan ini, tetapi setelah beberapa penelitian lebih lanjut menemukan bahwa itu lebih masuk akal dan menghasilkan kode yang lebih mudah dikelola dan dibaca untuk mengekspos peristiwa dan properti yang direktif akan digunakan untuk komunikasi melalui layanan kemudian menggunakan $ watch pada properti layanan di arahan atau kontrol lain yang perlu bereaksi terhadap perubahan tersebut untuk komunikasi.

Abstraksi ini bekerja sangat baik dengan kerangka injeksi ketergantungan AngularJS karena Anda dapat menyuntikkan layanan ke item apa pun yang perlu bereaksi terhadap peristiwa tersebut. Jika Anda melihat file Angular.js, Anda akan melihat bahwa arahan di sana juga menggunakan layanan dan $ menonton dengan cara ini, mereka tidak memaparkan peristiwa di ruang lingkup yang terisolasi.

Terakhir, dalam hal Anda perlu berkomunikasi antara arahan yang tergantung satu sama lain, saya akan merekomendasikan berbagi pengontrol antara arahan tersebut sebagai alat komunikasi.

Wiki AngularJS untuk Praktik Terbaik juga menyebutkan ini:

Hanya gunakan. $ Broadcast (),. $ Emit () dan. $ On () untuk acara atom Peristiwa yang relevan secara global di seluruh aplikasi (seperti autentikasi pengguna atau penutupan aplikasi). Jika Anda ingin acara khusus untuk modul, layanan, atau widget, Anda harus mempertimbangkan Layanan, Pengendali Arahan, atau Libs Pihak Ketiga

  • $ scope. $ watch () harus menggantikan kebutuhan untuk acara
  • Layanan injeksi dan metode panggilan secara langsung juga berguna untuk komunikasi langsung
  • Arahan dapat berkomunikasi secara langsung satu sama lain melalui pengontrol direktif
Selalu belajar
sumber
2
Saya mencapai dua solusi secara intuitif: (1) menonton perubahan variabel lingkup =, variabel berisi nama metode dan argumen. (2) memperlihatkan string satu arah mengikat @sebagai id topik dan biarkan callee mengirim acara tentang topik ini. Sekarang saya melihat wiki latihan terbaik. Saya pikir ada alasan untuk tidak melakukannya dengan cara Mei. Tapi saya masih belum begitu jelas, bagaimana cara kerjanya. Dalam kasus saya, saya membuat arahan tabset, saya ingin mengekspos switchTab(tabIndex)metode. Bisakah Anda memberi contoh lebih banyak?
stanleyxu2005
Anda tidak akan mengekspos switchTab(tabIndex)metode, Anda hanya akan mengikat ke tabIndexvariabel. Pengontrol halaman Anda mungkin memiliki tindakan yang mengubah variabel itu. Anda mengikat / meneruskan variabel itu ke Petunjuk tab Anda. Petunjuk tab Anda kemudian dapat menonton variabel itu untuk perubahan, dan melakukan switchTab atas kemauan sendiri. Karena arahan memutuskan kapan / bagaimana cara mengontrol tabnya berdasarkan variabel. Itu bukan pekerjaan sumber eksternal, jika tidak sumber eksternal membutuhkan pengetahuan tentang cara kerja direktif, yang mungkin buruk.
Suamere
15

Membangun berdasarkan jawaban Oliver - Anda mungkin tidak selalu perlu mengakses metode dalam direktif, dan dalam kasus-kasus itu Anda mungkin tidak ingin harus membuat objek kosong dan menambahkan controlattr ke direktif hanya untuk mencegahnya membuat kesalahan ( cannot set property 'takeTablet' of undefined).

Anda juga mungkin ingin menggunakan metode di tempat lain dalam arahan.

Saya akan menambahkan tanda centang untuk memastikan scope.controlada, dan mengatur metode untuk itu dengan cara yang mirip dengan pola modul mengungkapkan

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
        };
      }
    }
  };
});
Diskon Murah
sumber
tepat, menggunakan pola mengungkapkan dalam arahan membuat niat jauh lebih jelas. bagus
JSancho
12

Sejujurnya, saya tidak benar-benar yakin dengan jawaban apa pun di utas ini. Jadi, inilah solusi saya:

Pendekatan Directive Handler (Manajer)

Metode ini agnostik terhadap apakah arahan itu $scopeadalah yang dibagikan atau yang diisolasi

A factoryuntuk mendaftarkan instance direktif

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

Kode direktif, saya biasanya meletakkan semua logika yang tidak berurusan dengan DOM di dalam pengontrol direktif. Dan mendaftarkan instance controller di dalam handler kami

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

kode templat

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

Akses instance pengontrol menggunakan factory& jalankan metode terbuka untuk umum

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

Pendekatan sudut

Mengambil daun dari buku sudut tentang bagaimana mereka berurusan

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

menggunakan $ parse dan mendaftarkan controller pada $parentscope. Teknik ini tidak bekerja pada $scopearahan yang terisolasi .

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

Akses di dalam pengontrol menggunakan $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});
Mudassir Ali
sumber
"Pendekatan Angular" tampak hebat! Ada kesalahan ketik: $scope.fooseharusnya$scope.my_form
Daniel D
Nah, itu $scope.fookarena template kita adalah <div my-directive name="foo"></div>dan namenilai atribut adalah 'foo'. <formhanyalah contoh dari salah satu arahan sudut yang menggunakan teknik ini
Mudassir Ali
10

Agak terlambat, tetapi ini adalah solusi dengan lingkup terisolasi dan "peristiwa" untuk memanggil fungsi dalam direktif. Solusi ini terinspirasi oleh pos SO ini oleh satchmorun dan menambahkan modul dan API.

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

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

Buat API untuk berkomunikasi dengan arahan. The addUpdateEvent menambahkan acara ke array acara dan memperbaruiPeta panggilan setiap fungsi acara.

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

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

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

(Mungkin Anda harus menambahkan fungsionalitas untuk menghapus acara.)

Dalam arahan set referensi ke MapAPI dan tambahkan $ scope.updateMap sebagai acara ketika MapApi.updateMap dipanggil.

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

        }
    }
});

Di controller "main" tambahkan referensi ke MapApi dan panggil saja MapApi.updateMap () untuk memperbarui peta.

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

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}
AxdorphCoder
sumber
2
Proposal ini akan membutuhkan sedikit lebih banyak pekerjaan di dunia nyata ketika Anda memiliki banyak arahan dari jenis yang sama tergantung pada layanan API Anda. Anda pasti akan berada dalam situasi di mana Anda perlu menargetkan dan memanggil fungsi hanya dari satu arahan spesifik dan tidak semuanya. Apakah Anda ingin meningkatkan jawaban Anda dengan solusi untuk ini?
smajl
5

Anda dapat menentukan atribut DOM yang dapat digunakan untuk memungkinkan arahan untuk mendefinisikan fungsi pada lingkup induk. Lingkup induk kemudian dapat memanggil metode ini seperti yang lain. Di sini seorang plunker. Dan di bawah ini adalah kode yang relevan.

clearfn adalah atribut pada elemen direktif di mana ruang lingkup induk dapat melewati properti lingkup yang direktif kemudian dapat diatur ke fungsi yang memenuhi perilaku yang diinginkan.

<!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>
Trevor
sumber
Saya tidak mengerti mengapa ini bekerja .. apakah itu karena atribut yang jelas ada dalam ruang lingkup bagaimana?
Quinn Wilson
1
Ini menjadi bagian dari ruang lingkup direktif segera setelah Anda mendeklarasikannya (misalnya scope: { clearFn: '=clearfn' }).
Trevor
2

Cukup gunakan lingkup. $ Parent untuk menghubungkan fungsi yang dipanggil ke fungsi direktif

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

dalam HTML

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

Anda dapat memberi tahu nama metode untuk mengarahkan untuk menentukan mana yang ingin Anda panggil dari pengontrol tetapi tanpa ruang lingkup yang terpisah,

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>

Naveen raj
sumber
1

DIUJI Semoga ini bisa membantu seseorang.

Pendekatan sederhana saya (Pikirkan tag sebagai kode asli Anda)

<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>
Santosh Kumar
sumber
0

Mungkin ini bukan pilihan terbaik, tetapi Anda dapat melakukan angular.element("#element").isolateScope()atau $("#element").isolateScope()mengakses ruang lingkup dan / atau pengontrol arahan Anda.

Alex198710
sumber
0

Cara mendapatkan pengontrol direktif dalam pengontrol halaman:

  1. tulis custom directive untuk mendapatkan referensi ke directive controller dari elemen 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. gunakan di html pengontrol halaman:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. Gunakan pengontrol direktif di pengontrol halaman:

    vm.myDirectiveController.callSomeMethod();

Catatan: solusi yang diberikan hanya berfungsi untuk pengontrol arahan elemen (nama tag digunakan untuk mendapatkan nama arahan yang diinginkan).

Robert J
sumber
0

Solusi di bawah ini akan berguna ketika, Anda memiliki pengontrol (baik induk maupun direktif (terisolasi)) dalam format 'controller As'

seseorang mungkin menganggap ini berguna,

pengarahan :

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

Pengendali direktif:

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

function SetFitlersToDefaultValue() {
           //your logic
        }
}

kode 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
Raunak Mali
sumber