โทร AngularJS จากรหัสเดิม


180

ฉันใช้ AngularJS เพื่อสร้างตัวควบคุม HTML ที่โต้ตอบกับแอปพลิเคชัน Flex แบบดั้งเดิม การเรียกกลับทั้งหมดจากแอพ Flex ต้องเชื่อมต่อกับหน้าต่าง DOM

ตัวอย่าง (ใน AS3)

ExternalInterface.call("save", data);

จะโทร

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

จากภายในฟังก์ชั่นปรับขนาด JS ฉันต้องการส่งเหตุการณ์ที่คอนโทรลเลอร์สามารถได้ยินได้ ดูเหมือนว่าการสร้างบริการเป็นวิธีที่จะไป คุณสามารถอัพเดทบริการจากด้านนอกของ AngularJS ได้หรือไม่? คอนโทรลเลอร์สามารถฟังเหตุการณ์จากบริการได้หรือไม่? ในการทดสอบหนึ่งครั้ง (คลิกเพื่อเล่นซอ)ฉันทำเหมือนว่าฉันสามารถเข้าถึงบริการได้ แต่การอัปเดตข้อมูลของบริการไม่ได้รับผลกระทบในมุมมอง (ในตัวอย่างที่<option>ควรเพิ่มใน<select>)

ขอบคุณ!


1
โปรดสังเกตว่าใน jsfiddle เหนือหัวฉีดจะได้รับโดยไม่ต้องกำหนดเป้าหมายองค์ประกอบภายใน app var injector = angular.injector(['ng', 'MyApp']);ที่ใช้ myServiceการทำเช่นนี้จะทำให้คุณมีบริบทใหม่ที่สมบูรณ์และที่ซ้ำกัน นั่นหมายความว่าคุณจะได้รับบริการและรุ่นของอินสแตนซ์ที่สองและจะเพิ่มข้อมูลไปยังตำแหน่งที่ไม่ถูกต้อง คุณควรกำหนดเป้าหมายองค์ประกอบภายในแอปที่ใช้angular.element('#ng-app').injector(['ng', 'MyApp'])แทน ณ จุดนี้คุณสามารถใช้ $ ใช้เพื่อตัดการเปลี่ยนแปลงโมเดล
Thanh Nguyen

คำตอบ:


293

การทำงานร่วมกันจากด้านนอกของเชิงมุมถึงเชิงมุมเหมือนกับการดีบักแอปพลิเคชันเชิงมุมหรือการรวมเข้ากับห้องสมุดบุคคลที่สาม

สำหรับองค์ประกอบ DOM ใด ๆ ที่คุณสามารถทำได้:

  • angular.element(domElement).scope() เพื่อรับขอบเขตปัจจุบันสำหรับองค์ประกอบ
  • angular.element(domElement).injector() เพื่อรับหัวฉีดแอปปัจจุบัน
  • angular.element(domElement).controller()เพื่อรับng-controllerอินสแตนซ์

จากหัวฉีดคุณสามารถรับบริการใด ๆ ในแอปพลิเคชันเชิงมุม ในทำนองเดียวกันจากขอบเขตที่คุณสามารถเรียกใช้วิธีการใด ๆ ที่ได้รับการเผยแพร่ไป

โปรดทราบว่าการเปลี่ยนแปลงใด ๆ กับโมเดลเชิงมุมหรือการเรียกใช้เมธอดใด ๆ บนขอบเขตจำเป็นต้องถูกห่อหุ้มใน$apply()ลักษณะนี้:

$scope.$apply(function(){
  // perform any model changes or method invocations here on angular app.
});

1
ทำงานได้ แต่ฉันหวังว่าจะมีวิธีที่จะได้รับจากโมดูลโดยตรงกับขอบเขต - เป็นไปได้หรือไม่ ต้องกลับไปเลือก[ng-app]รูตโหนดดูเหมือนว่าย้อนกลับเมื่อฉันมีการอ้างอิงถึงโมดูล ...
mindplay.dk

5
ฉันไม่สามารถทำงานนี้ได้: ฉันกำลังโทรangular.element(document.getElementById(divName)).scope()แต่ฉันไม่สามารถเรียกใช้ฟังก์ชั่นใด ๆ จากมันเพียงแค่ส่งกลับ "undefined" ในคอนโซล
Emil

1
แม้ว่าฉันจะเผชิญกับปัญหาเดียวกันตามที่อธิบายไว้ข้างต้นโดย @Emil แต่ก็กลับไม่ได้กำหนด ความช่วยเหลือใด ๆ
Bibin

5
องค์ประกอบ (). scope () จะไม่ทำงานหากข้อมูลการดีบักถูกปิดซึ่งเป็นคำแนะนำสำหรับการผลิต นั่นไม่ได้ทำให้ไร้ประโยชน์ในสถานการณ์นี้ใช่ไหม จะใช้สำหรับการทดสอบ / การดีบักเท่านั้น
K. Norbert

4
คุณไม่ต้องการทำสิ่งนี้ใน Angular 1.3 ทีมเชิงมุมไม่ได้ต้องการให้เราเรียก ".scope ()" เกี่ยวกับองค์ประกอบในรหัสการผลิต มันหมายถึงการเป็นเครื่องมือแก้ปัญหา ดังนั้นใน Angular 1.3 คุณสามารถปิดสิ่งนี้ได้ เชิงมุมจะหยุดการแนบขอบเขตเข้ากับองค์ประกอบโดยใช้ฟังก์ชัน. jata ของ jQuery นี่จะทำให้แอปของคุณเร็วขึ้น นอกจากนี้การส่งขอบเขตของคุณไปยังคุณสมบัติการแคชของ jQuery จะสร้างการรั่วไหลของหน่วยความจำ ดังนั้นคุณควรปิดการใช้งานนี้เพื่อเร่งความเร็วแอปของคุณ ไซต์ของ Angular มีคู่มือการผลิตที่คุณควรใช้เพื่อเรียนรู้เพิ่มเติม
หนาวจัด

86

Misko ให้คำตอบที่ถูกต้อง (ชัด) แต่พวกเรามือใหม่บางคนอาจต้องการความเรียบง่ายเพิ่มเติม

เมื่อเรียกรหัส AngularJS จากภายในแอปรุ่นเก่าให้นึกถึงรหัส AngularJS ว่าเป็น "ไมโครแอป" ที่มีอยู่ในที่เก็บที่มีการป้องกันในแอปพลิเคชันรุ่นเก่าของคุณ คุณไม่สามารถโทรได้โดยตรง (ด้วยเหตุผลที่ดีมาก) แต่คุณสามารถโทรจากระยะไกลผ่านทางวัตถุ $ scope

ในการใช้ออบเจ็กต์ $ scope คุณต้องได้รับการจัดการของ $ scope โชคดีที่มันง่ายมากที่จะทำ

คุณสามารถใช้รหัสขององค์ประกอบ HTML ใด ๆ ภายใน AngularJS "micro-app" HTML ของคุณเพื่อรับขอบเขตของแอพ AngularJS $ $

ตัวอย่างเช่นสมมติว่าเราต้องการเรียกฟังก์ชั่นสองสามอย่างภายในคอนโทรลเลอร์ AngularJS ของเราเช่น sayHi () และ sayBye () ใน AngularJS HTML (ดู) เรามี div ที่มี id "MySuperAwesomeApp" คุณสามารถใช้รหัสต่อไปนี้รวมกับ jQuery เพื่อรับค่า $ scope:

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

ตอนนี้คุณสามารถเรียกใช้ฟังก์ชันโค้ด AngularJS ของคุณผ่านการจัดการขอบเขต:

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

microappscope.sayHi();

microappscope.sayBye();

เพื่อให้สิ่งต่าง ๆ สะดวกยิ่งขึ้นคุณสามารถใช้ฟังก์ชั่นเพื่อจับที่จับขอบเขตเมื่อใดก็ได้ที่คุณต้องการเข้าถึง:

function microappscope(){

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

}

การโทรของคุณจะเป็นดังนี้:

microappscope().sayHi();

microappscope().sayBye();

คุณสามารถดูตัวอย่างการทำงานได้ที่นี่:

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

ฉันยังแสดงสิ่งนี้ในสไลด์โชว์สำหรับกลุ่ม Ottawa AngularJS (เพียงข้ามไปที่สไลด์ 2 แผ่นสุดท้าย)

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


4
โปรดทราบว่าคำตอบที่ลิงก์อย่างเดียวนั้นไม่ได้รับการสนับสนุนดังนั้นคำตอบ SO ควรเป็นจุดสิ้นสุดของการค้นหาวิธีแก้ไข (เทียบกับการหยุดพักระหว่างการอ้างอิงอื่นซึ่งมักจะค้างเมื่อเวลาผ่านไป) โปรดลองเพิ่มบทสรุปแบบสแตนด์อโลนที่นี่โดยคงลิงก์ไว้เป็นข้อมูลอ้างอิง
kleopatra

ชี้แจงเพิ่มเติมที่ดี ขอบคุณ
โจ

1
คำอธิบายที่สวยงาม! อนุญาตให้ฉันหลีกเลี่ยงการตรวจสอบแบบฟอร์มโดยทำสิ่งนี้:<input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete">
Purefan

24

คำอธิบายที่ดีที่สุดของแนวคิดที่ฉันพบอยู่ที่นี่: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

เพื่อช่วยให้คุณประหยัดการคลิก:

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

14
ด้านบนใช้งานได้เมื่อแอพและคอนโทรลเลอร์มีอยู่ในองค์ประกอบเดียวกัน สำหรับแอปที่ซับซ้อนมากขึ้นซึ่งใช้คำสั่ง ng-view กับเทมเพลตคุณจะต้องได้รับองค์ประกอบแรกในมุมมองไม่ใช่องค์ประกอบ DOM ของแอปทั้งหมด ฉันต้องสะกิดองค์ประกอบด้วย document.getElementsByClassName ('ng-scope'); รายการโหนดที่จะคิดออกองค์ประกอบ DOM ขอบเขตที่ถูกต้องที่จะคว้า
goosemanjack

ฉันรู้ว่านี่เป็นเรื่องเก่ามาก แต่ฉันคิดว่าฉันกำลังประสบกับปัญหานี้ ใครบ้างมีรหัสที่แสดงวิธีการเดินรายการเพื่อคิดออกองค์ประกอบ DOM ที่จะคว้า?
JerryKur

ไม่สนใจคำถามของฉัน ฉันสามารถทำงานนี้ได้โดยใช้ document.getElementById ('any-Control-That-Has-An-NG-Directive'). ขอบเขต ()
JerryKur

หากคุณใช้มุมมอง ng และแยกมุมมองเป็นไฟล์ของตัวเอง คุณสามารถใส่และidในองค์ประกอบ HTML ด้านบนจากนั้นทำdocument.getElementById()รหัสนั้น สิ่งนี้ช่วยให้คุณสามารถเข้าถึงขอบเขตของคอนโทรลเลอร์นั้นได้ วิธีการ / คุณสมบัติ ฯลฯ ... เพียงแค่วางจุดที่ดีในการแสดงความคิดเห็นของ goosemanjack
ftravers

13

ยิ่งไปกว่าคำตอบอื่น ๆ หากคุณไม่ต้องการเข้าถึงวิธีการในคอนโทรลเลอร์ แต่ต้องการเข้าถึงบริการโดยตรงคุณสามารถทำสิ่งนี้ได้:

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

13

ขอบคุณโพสต์ก่อนหน้านี้ฉันสามารถอัปเดตโมเดลของฉันด้วยเหตุการณ์แบบอะซิงโครนัส

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

ฉันประกาศแบบจำลองของฉัน

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

และฉันอัปเดตโมเดลของฉันจากนอกขอบเขตของฉัน

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

6

วิธีที่ปลอดภัยและมีประสิทธิภาพยิ่งขึ้นโดยเฉพาะอย่างยิ่งเมื่อปิดการแก้ไขข้อมูลคือใช้ตัวแปรที่แชร์เพื่อเก็บฟังก์ชั่นการโทรกลับ คอนโทรลเลอร์เชิงมุมของคุณใช้ฟังก์ชั่นนี้เพื่อส่งกลับ internals ไปยังรหัสภายนอก

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

ทั่วไปเป็นคำสั่งนำมาใช้ใหม่:

ฉันสร้างคำสั่ง 'exposeScope' ซึ่งทำงานในรูปแบบที่คล้ายกัน แต่การใช้งานนั้นง่ายกว่า:

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

นี้เก็บขอบเขตปัจจุบัน (ที่มอบให้กับฟังก์ชั่นการเชื่อมโยงของคำสั่ง) ในวัตถุ 'ขอบเขต' ทั่วโลกซึ่งเป็นผู้ถือสำหรับขอบเขตทั้งหมด ค่าที่ระบุให้กับแอตทริบิวต์คำสั่งจะใช้เป็นชื่อคุณสมบัติของขอบเขตในวัตถุส่วนกลางนี้

ดูการสาธิตที่นี่ ดังที่ฉันแสดงในการสาธิตคุณสามารถทริกเกอร์เหตุการณ์ jQuery เมื่อขอบเขตถูกเก็บและลบออกจากวัตถุ 'ขอบเขต' ทั่วโลก

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

โปรดทราบว่าฉันยังไม่ได้ทดสอบ on ('scopeDestroyed') เมื่อองค์ประกอบที่แท้จริงถูกลบออกจาก DOM ถ้ามันไม่ทำงานการทริกเกอร์เหตุการณ์บนเอกสารแทนองค์ประกอบอาจช่วยได้ (ดูที่ app.js) สคริปต์ในโปรแกรมสาธิตพลั่ว


3

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันดูตัวเลือกในการทำเช่นนี้เมื่อเร็ว ๆ นี้ดังนั้นฉันคิดว่าฉันใส่สิ่งที่ฉันค้นพบที่นี่ในกรณีที่เป็นประโยชน์กับทุกคน

ในกรณีส่วนใหญ่หากจำเป็นต้องใช้รหัสดั้งเดิมภายนอกเพื่อโต้ตอบกับสถานะของ UI หรือการทำงานภายในของแอปพลิเคชันบริการอาจมีประโยชน์ในการแยกการเปลี่ยนแปลงเหล่านั้นออก หากรหัสภายนอกมีการโต้ตอบโดยตรงกับตัวควบคุมเชิงมุมส่วนประกอบหรือคำสั่งของคุณคุณกำลังเชื่อมโยงแอปของคุณอย่างหนักกับรหัสดั้งเดิมซึ่งเป็นข่าวร้าย

สิ่งที่ฉันใช้ในกรณีของฉันคือการรวมกันของ globals สามารถเข้าถึงเบราว์เซอร์ (เช่นหน้าต่าง) และการจัดการเหตุการณ์ รหัสของฉันมีเอ็นจิ้นการสร้างแบบฟอร์มอัจฉริยะซึ่งต้องการเอาต์พุต JSON จาก CMS เพื่อเริ่มต้นฟอร์ม นี่คือสิ่งที่ฉันได้ทำ:

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

บริการ Schema แบบฟอร์มถูกสร้างขึ้นโดยใช้หัวฉีดเชิงมุมตามที่คาดไว้:

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

และในตัวควบคุมของฉัน: function () {'ใช้เข้มงวด';

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

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

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

จนถึงตอนนี้นี่คือการเขียนโปรแกรมเชิงบริการเชิงบริสุทธิ์และเชิงจาวาสคริปต์ แต่การรวมมรดกมาที่นี่:

<script type="text/javascript">

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

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

 })(window);
</script>

เห็นได้ชัดว่าทุกวิธีมีข้อดีและข้อเสีย ข้อดีและการใช้วิธีนี้ขึ้นอยู่กับ UI ของคุณ แนวทางที่แนะนำก่อนหน้านี้ไม่ทำงานในกรณีของฉันเนื่องจากสคีมาของฉันและรหัสดั้งเดิมไม่มีการควบคุมและความรู้เกี่ยวกับขอบเขตเชิงมุม ดังนั้นการกำหนดค่าแอพของฉันตามangular.element('element-X').scope(); อาจทำให้แอพแตกถ้าเราเปลี่ยนขอบเขต แต่ถ้าแอพของคุณมีความรู้เกี่ยวกับการกำหนดขอบเขตและสามารถพึ่งพามันได้ไม่เปลี่ยนแปลงบ่อยครั้งสิ่งที่แนะนำก่อนหน้านี้คือวิธีการปฏิบัติ

หวังว่านี่จะช่วยได้ ข้อเสนอแนะใด ๆ ก็ยินดีต้อนรับ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.