Panggil AngularJS dari kode lawas

180

Saya menggunakan AngularJS untuk membangun kontrol HTML yang berinteraksi dengan aplikasi Flex yang lama. Semua panggilan balik dari aplikasi Flex harus dilampirkan ke jendela DOM.

Misalnya (dalam AS3)

ExternalInterface.call("save", data);

Akan menelepon

window.save = function(data){
    // want to update a service 
    // or dispatch an event here...
}

Dari dalam fungsi pengubahan ukuran JS Saya ingin mengirim acara yang dapat didengar oleh pengontrol. Tampaknya menciptakan layanan adalah cara yang harus dilakukan. Bisakah Anda memperbarui layanan dari luar AngularJS? Bisakah seorang pengontrol mendengarkan acara dari suatu layanan? Dalam satu percobaan (klik untuk biola) saya lakukan sepertinya saya dapat mengakses layanan tetapi memperbarui data layanan tidak tercermin dalam tampilan (dalam contoh <option>harus ditambahkan ke <select>).

Terima kasih!

kreek
sumber
1
Perhatikan bahwa dalam jsfiddle di atas injektor diperoleh tanpa menargetkan elemen dalam aplikasi menggunakan var injector = angular.injector(['ng', 'MyApp']);. Melakukan ini akan memberi Anda konteks yang sama sekali baru dan duplikat myService. Itu berarti Anda akan berakhir dengan dua contoh layanan dan model dan akan menambahkan data ke tempat yang salah. Anda sebaiknya menargetkan elemen di dalam aplikasi menggunakan angular.element('#ng-app').injector(['ng', 'MyApp']). Pada titik ini Anda dapat menggunakan $ apply untuk membungkus perubahan model.
Thanh Nguyen

Jawaban:

293

Interop dari luar sudut ke sudut sama dengan debugging aplikasi sudut atau mengintegrasikan dengan perpustakaan pihak ketiga.

Untuk setiap elemen DOM Anda dapat melakukan ini:

  • angular.element(domElement).scope() untuk mendapatkan cakupan elemen saat ini
  • angular.element(domElement).injector() untuk mendapatkan injektor aplikasi saat ini
  • angular.element(domElement).controller()untuk mendapatkan ng-controllercontoh.

Dari injektor Anda dapat memperoleh layanan apa pun dalam aplikasi sudut. Demikian pula dari ruang lingkup Anda dapat memanggil metode apa pun yang telah dipublikasikan untuk itu.

Perlu diingat bahwa setiap perubahan pada model sudut atau setiap pemanggilan metode pada ruang lingkup perlu dibungkus $apply()seperti ini:

$scope.$apply(function(){
  // perform any model changes or method invocations here on angular app.
});
Misko Hevery
sumber
1
ini berfungsi, tapi saya berharap ada beberapa cara untuk mendapatkan dari Modul langsung ke cakupannya - apakah itu mungkin? Harus kembali pilih [ng-app]root-node tampaknya mundur ketika saya sudah memiliki referensi ke Modul ...
mindplay.dk
5
Saya angular.element(document.getElementById(divName)).scope()tidak dapat mengaktifkan ini: Saya menelepon , tetapi saya tidak dapat menjalankan fungsi apa pun darinya, itu hanya mengembalikan "tidak terdefinisi" di konsol.
Emil
1
Bahkan saya menghadapi masalah yang sama seperti dijelaskan di atas oleh @ Emil, itu kembali tidak terdefinisi. Ada bantuan?
Bibin
5
element (). scope () tidak akan berfungsi jika data debug dimatikan, yang merupakan rekomendasi untuk produksi. Bukankah itu membuatnya tidak berguna dalam skenario ini? Ini hanya untuk pengujian / debugging.
K. Norbert
4
Anda tidak akan mau melakukan ini dalam Angular 1.3. Tim Angular tidak bermaksud meminta kami memanggil ".scope ()" pada elemen dalam kode produksi. Itu dimaksudkan untuk menjadi alat debug. Jadi, mulai dari Angular 1.3, Anda dapat mematikannya. Angular akan berhenti melampirkan lingkup ke elemen menggunakan fungsi .data jQuery. Ini akan mempercepat aplikasi Anda. Selain itu, menyerahkan lingkup Anda ke fitur caching jquery akan membuat kebocoran memori. Jadi, Anda harus mematikan ini, untuk mempercepat aplikasi Anda. Situs Angular memiliki panduan produksi yang harus Anda gunakan untuk mempelajari lebih lanjut.
sangat dingin
86

Misko memberikan jawaban yang benar (jelas), tetapi beberapa dari kita pemula mungkin perlu disederhanakan lebih lanjut.

Ketika datang untuk memanggil kode AngularJS dari dalam aplikasi legacy, anggap kode AngularJS sebagai "aplikasi mikro" yang ada dalam wadah yang dilindungi dalam aplikasi legacy Anda. Anda tidak dapat membuat panggilan secara langsung (untuk alasan yang sangat bagus), tetapi Anda dapat membuat panggilan jarak jauh melalui objek $ scope.

Untuk menggunakan objek $ scope, Anda perlu menggunakan $ scope. Untungnya ini sangat mudah dilakukan.

Anda dapat menggunakan id elemen HTML apa pun di dalam HTML "aplikasi mikro" AngularJS Anda untuk mendapatkan pegangan lingkup aplikasi $ AngularJS.

Sebagai contoh, katakanlah kita ingin memanggil beberapa fungsi di dalam pengontrol AngularJS kita seperti sayHi () dan sayBye (). Dalam AngularJS HTML (tampilan) kami memiliki div dengan id "MySuperAwesomeApp". Anda dapat menggunakan kode berikut, dikombinasikan dengan jQuery untuk mendapatkan pegangan $ lingkup:

var microappscope = angular.element($("#MySuperAwesomeApp")).scope();

Sekarang Anda dapat memanggil fungsi kode AngularJS Anda dengan cara menangani lingkup:

// we are in legacy code land here...

microappscope.sayHi();

microappscope.sayBye();

Agar lebih nyaman, Anda dapat menggunakan fungsi untuk mengambil pegangan lingkup kapan pun Anda ingin mengaksesnya:

function microappscope(){

    return angular.element($("#MySuperAwesomeApp")).scope();

}

Panggilan Anda akan terlihat seperti ini:

microappscope().sayHi();

microappscope().sayBye();

Anda dapat melihat contoh kerja di sini:

http://jsfiddle.net/peterdrinnan/2nPnB/16/

Saya juga menunjukkan ini dalam tayangan slide untuk grup Ottawa AngularJS (lewati saja 2 slide terakhir)

http://www.slideshare.net/peterdrinnan/angular-for-legacyapps

Peter Drinnan
sumber
4
Perhatikan bahwa jawaban hanya tautan tidak disarankan, jawaban SO harus menjadi titik akhir dari pencarian solusi (vs. persinggahan referensi lainnya, yang cenderung menjadi basi seiring waktu). Harap pertimbangkan untuk menambahkan sinopsis mandiri di sini, dengan menjaga tautan sebagai referensi.
kleopatra
Klarifikasi tambahan yang bagus. Terima kasih.
Joe
1
Penjelasan yang indah! mengizinkan saya menghindari validasi formulir dengan melakukan ini:<input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete">
Purefan
24

Penjelasan terhebat dari konsep yang saya temukan ada di sini: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

Untuk menghemat klik:

// get Angular scope from the known DOM element
e = document.getElementById('myAngularApp');
scope = angular.element(e).scope();
// update the model with a wrap in $apply(fn) which will refresh the view for us
scope.$apply(function() {
    scope.controllerMethod(val);
}); 
Orang bijak
sumber
14
Hal di atas berfungsi ketika aplikasi dan pengontrol ada di elemen yang sama. Untuk aplikasi yang lebih kompleks yang menggunakan arahan tampilan-ng ke template, Anda harus mendapatkan elemen pertama dalam tampilan, bukan elemen DOM dari seluruh aplikasi. Saya harus melihat-lihat elemen dengan dokumen.getElementsByClassName ('ng-scope'); daftar simpul untuk mengetahui elemen DOM lingkup yang benar untuk ambil.
goosemanjack
Saya tahu ini adalah utas yang sangat lama, tetapi saya pikir saya mengalami masalah ini. Apakah ada yang punya kode yang menunjukkan cara berjalan daftar untuk mencari tahu elemen DOM untuk ambil?
JerryKur
Abaikan pertanyaan saya. Saya bisa mendapatkan pekerjaan ini hanya dengan menggunakan document.getElementById ('any-Control-That-Has-An-NG-Directive'). Scope ().
JerryKur
jika Anda memanfaatkan ng-view dan membagi tampilan Anda menjadi file mereka sendiri. Anda dapat meletakkan dan iddi elemen HTML atas kemudian melakukan document.getElementById()id itu. Ini memberi Anda akses ke lingkup pengontrol itu. metode / properti dll ... hanya memberikan poin bagus pada komentar goosemanjack.
ftravers
13

Lebih jauh ke jawaban lain. Jika Anda tidak ingin mengakses metode dalam pengontrol tetapi ingin mengakses layanan secara langsung, Anda dapat melakukan sesuatu seperti ini:

// Angular code* :
var myService = function(){
    this.my_number = 9;
}
angular.module('myApp').service('myService', myService);


// External Legacy Code:
var external_access_to_my_service = angular.element('body').injector().get('myService');
var my_number = external_access_to_my_service.my_number 
Alec Hewitt
sumber
13

Berkat posting sebelumnya, saya dapat memperbarui model saya dengan acara asinkron.

<div id="control-panel" ng-controller="Filters">
    <ul>
        <li ng-repeat="filter in filters">
        <button type="submit" value="" class="filter_btn">{{filter.name}}</button>
        </li>
    </ul>
</div>

Saya menyatakan model saya

function Filters($scope) {
    $scope.filters = [];
}

Dan saya memperbarui model saya dari luar ruang lingkup saya

ws.onmessage = function (evt) {
    dictt = JSON.parse(evt.data);
    angular.element(document.getElementById('control-panel')).scope().$apply(function(scope){
        scope.filters = dictt.filters;
    });
};
Guillaume Vincent
sumber
6

Cara yang lebih aman dan berkinerja terutama ketika men-debug data tidak aktif adalah dengan menggunakan variabel bersama untuk menahan fungsi panggilan balik. Pengontrol sudut Anda mengimplementasikan fungsi ini untuk mengembalikan internalnya ke kode eksternal.

var sharedVar = {}
myModule.constant('mySharedVar', sharedVar)
mymodule.controller('MyCtrl', [ '$scope','mySharedVar', function( $scope, mySharedVar) {

var scopeToReturn = $scope;

$scope.$on('$destroy', function() {
        scopeToReturn = null;
    });

mySharedVar.accessScope = function() {
    return scopeToReturn;
}
}]);

Generalisasi sebagai arahan yang dapat digunakan kembali:

Saya membuat arahan 'exposeScope' yang bekerja dengan cara yang serupa tetapi penggunaannya lebih sederhana:

<div ng-controller="myController" expose-scope="aVariableNameForThisScope">
   <span expose-scope='anotherVariableNameForTheSameScope" />
</div>

Ini menyimpan lingkup saat ini (yang diberikan ke fungsi tautan direktif) dalam objek 'lingkup' global yang merupakan pemegang untuk semua cakupan. Nilai yang diberikan ke atribut direktif digunakan sebagai nama properti lingkup di objek global ini.

Lihat demo di sini . Seperti yang saya tunjukkan dalam demo, Anda dapat memicu peristiwa jQuery ketika cakupan disimpan dan dihapus dari objek 'cakupan' global.

<script type="text/javascript" >
    $('div').on('scopeLinked', function(e, scopeName, scope, allScopes) {
      // access the scope variable or the given name or the global scopes object
    }.on('scopeDestroyed', function(e, scopeName, scope, allScopes) {
      // access the scope variable or the given name or the global scopes object
    }

</script>

Perhatikan bahwa, saya belum menguji on ('scopeDestroyed') ketika elemen sebenarnya dihapus dari DOM. Jika tidak berhasil, memicu acara pada dokumen itu sendiri dan bukan elemen yang dapat membantu. (lihat skrip app.js) di dalam demo plunker.

Cagatay Kalan
sumber
3

Saya tahu ini adalah pertanyaan lama tetapi saya sedang mencari opsi untuk melakukan ini baru-baru ini, jadi saya pikir saya menaruh temuan saya di sini kalau-kalau itu berguna bagi siapa pun.

Dalam kebanyakan kasus, jika ada kebutuhan untuk kode warisan eksternal untuk berinteraksi dengan keadaan UI atau cara kerja aplikasi, layanan dapat berguna untuk menghilangkan perubahan-perubahan tersebut. Jika kode eksternal berinteraksi langsung dengan pengontrol sudut, komponen, atau arahan Anda, Anda sangat menyambungkan aplikasi Anda dengan kode lawas yang merupakan berita buruk.

Apa yang akhirnya saya gunakan dalam kasus saya, adalah kombinasi global yang dapat diakses browser (yaitu jendela) dan penanganan acara. Kode saya memiliki mesin pembuat formulir cerdas yang membutuhkan output JSON dari CMS untuk memulai formulir. Inilah yang telah saya lakukan:

function FormSchemaService(DOM) {
    var conf = DOM.conf;

    // This event is the point of integration from Legacy Code 
    DOM.addEventListener('register-schema', function (e) {

       registerSchema(DOM.conf); 
    }, false);

    // service logic continues ....

Formulir Layanan Skema dibuat menggunakan injektor sudut seperti yang diharapkan:

angular.module('myApp.services').
service('FormSchemaService', ['$window' , FormSchemaService ])

Dan di controller saya: function () {'use strict';

angular.module('myApp').controller('MyController', MyController);

MyEncapsulatorController.$inject = ['$scope', 'FormSchemaService'];

function MyController($scope, formSchemaService) {
    // using the already configured formSchemaService
    formSchemaService.buildForm(); 

Sejauh ini ini adalah pemrograman berorientasi layanan murni javascript dan javascript. Tetapi integrasi warisan datang ke sini:

<script type="text/javascript">

   (function(app){
        var conf = app.conf = {
       'fields': {
          'field1: { // field configuration }
        }
     } ; 

     app.dispatchEvent(new Event('register-schema'));

 })(window);
</script>

Jelas setiap pendekatan memiliki kelebihan dan kekurangannya. Keuntungan dan penggunaan pendekatan ini tergantung pada UI Anda. Pendekatan yang disarankan sebelumnya tidak berfungsi dalam kasus saya karena skema bentuk dan kode lama saya tidak memiliki kontrol dan pengetahuan tentang cakupan sudut. Oleh karena itu mengonfigurasi aplikasi saya berdasarkan angular.element('element-X').scope(); berpotensi dapat merusak aplikasi jika kita mengubah cakupannya. Tetapi jika aplikasi Anda memiliki pengetahuan tentang pelingkupan dan dapat mengandalkannya agar tidak sering berubah, apa yang disarankan sebelumnya adalah pendekatan yang layak.

Semoga ini membantu. Umpan balik juga diterima.

Shakus
sumber