从遗留代码中调用AngularJS

时间:2012-05-07 23:35:52

标签: javascript actionscript-3 externalinterface angularjs

我正在使用AngularJS构建与传统Flex应用程序交互的HTML控件。 Flex应用程序中的所有回调都必须附加到DOM窗口。

例如(在AS3中)

ExternalInterface.call("save", data);

将致电

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

在JS调整大小功能中,我想发送一个控制器可以听到的事件。似乎创建服务是最佳选择。你能从AngularJS外部更新服务吗?控制器可以监听服务中的事件吗?在一个experiment (click for fiddle)中,我似乎可以访问服务,但更新服务的数据不会反映在视图中(在示例中,<option>应添加到<select> )。

谢谢!

7 个答案:

答案 0 :(得分:292)

从角度到角度之外的互操作与调试角度应用或与第三方库集成相同。

对于任何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 :(得分:86)

Misko给出了正确的答案(显然),但是我们中的一些新手可能需要进一步简化。

如果要从旧版应用程序中调用AngularJS代码,请将AngularJS代码视为旧应用程序中受保护容器中存在的“微应用程序”。你无法直接调用它(出于很好的理由),但你可以通过$ scope对象进行远程调用。

要使用$ scope对象,您需要获取$ scope的句柄。幸运的是,这很容易做到。

您可以使用AngularJS“micro-app”HTML中任何HTML元素的ID来获取AngularJS app $ scope的句柄。

作为一个例子,假设我们想在AngularJS控制器中调用几个函数,例如sayHi()和sayBye()。在AngularJS HTML(视图)中,我们有一个id为“MySuperAwesomeApp”的div。您可以使用以下代码,结合使用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/

我还在渥太华AngularJS小组的幻灯片中展示了这一点(只是跳到最后2张幻灯片)

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

答案 2 :(得分: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);
}); 

答案 3 :(得分: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;
    });
};

答案 4 :(得分:12)

进一步回答其他问题。 如果您不想访问控制器中的方法但想直接访问该服务,可以执行以下操作:

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

答案 5 :(得分:6)

更安全和高效的方式,尤其是在调试数据关闭时,是使用共享变量来保存回调函数。角度控制器实现此功能以将其内部结构返回到外部代码。

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>

这将当前作用域(指令的链接函数)存储在全局“作用域”对象中,该对象是所有作用域的持有者。提供给directive属性的值用作此全局对象中作用域的属性名称。

参见演示here。正如我在演示中所展示的那样,当存储范围并从全局“范围”对象中删除时,您可以触发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>

注意,当从DOM中删除实际元素时,我还没有测试过on('scopeDestroyed')。如果它不起作用,则在文档本身而不是元​​素上触发事件可能会有所帮助。 (请参阅demo plunker中的app.js)脚本。

答案 6 :(得分:3)

我知道这是一个老问题,但我最近正在考虑选择这样做,所以我认为我把我的发现放在这里,以防它对任何人都有用。

在大多数情况下,如果需要外部遗留代码与UI的状态或应用程序的内部工作进行交互,则服务可能有助于抽象出这些更改。如果外部代码直接与角度控制器,组件或指令进行交互,那么您的应用程序会严重地与您的遗留代码耦合,这是个坏消息。

我最终使用的是浏览器可访问的全局(即窗口)和事件处理的组合。我的代码有一个智能表单生成引擎,它需要CMS的JSON输出来初始化表单。这就是我所做的:

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

使用角度注入器按预期创建表单架构服务:

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

在我的控制器中: function(){     &#39;使用严格的&#39;;

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

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

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

到目前为止,这是纯粹的角度和javascript服务导向编程。但遗留的整合来自:

<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();配置我的应用 如果我们改变范围,可能会破坏应用程序。但是,如果您的应用程序了解范围界定并且可以依赖它不经常更改,那么之前建议的是一种可行的方法。

希望这会有所帮助。任何反馈也是受欢迎的。