从遗留代码调用 AngularJS

Posted

技术标签:

【中文标题】从遗留代码调用 AngularJS【英文标题】:Call AngularJS from legacy code 【发布时间】:2012-05-16 11:05:15 【问题描述】:

我正在使用 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>)。

谢谢!

【问题讨论】:

请注意,在上面的 jsfiddle 中,注入器是在没有使用 var injector = angular.injector(['ng', 'MyApp']); 定位应用程序内的元素的情况下获得的。这样做会给你一个全新的上下文和一个重复的myService。这意味着您最终会得到服务和模型的两个实例,并且会将数据添加到错误的位置。您应该改为使用 angular.element('#ng-app').injector(['ng', 'MyApp']) 定位应用程序中的元素。此时您可以使用 $apply 来包装模型更改。 【参考方案1】:

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

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>

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

查看演示 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')。如果它不起作用,则在文档本身而不是元​​素上触发事件可能会有所帮助。 (参见 app.js )演示插件中的脚本。

【讨论】:

【参考方案2】:

我知道这是一个老问题,但我最近正在寻找解决方法,所以我想我把我的发现放在这里,以防它对任何人有用。

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

我最终使用的是浏览器可访问的全局变量(即 window )和事件处理的组合。我的代码有一个智能表单生成引擎,它需要来自 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 ....

Form Schema Service 按预期使用 Angular 注入器创建:

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

在我的控制器中: 功能 () '使用严格';

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

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

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

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

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

【讨论】:

【参考方案3】:

感谢上一篇文章,我可以使用异步事件更新我的模型。

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

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

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

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

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

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

您可以使用 AngularJS“微应用”HTML 中任何 HTML 元素的 id 来获取 AngularJS 应用 $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/

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

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

【讨论】:

请注意,不鼓励仅链接的答案,因此答案应该是搜索解决方案的终点(相对于另一个参考中途停留,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。 很好的补充说明。谢谢。 美丽的解释!允许我通过这样做来规避表单验证:&lt;input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete"&gt;【参考方案6】:

我发现的概念的最佳解释位于此处: 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);
); 

【讨论】:

当应用程序和控制器共存于同一个元素中时,上述方法有效。对于将 ng-view 指令用于模板的更复杂的应用程序,您必须获取视图中的第一个元素,而不是整个应用程序的 DOM 元素。我不得不用 document.getElementsByClassName('ng-scope');节点列表找出要抓取的正确范围 DOM 元素。 我知道这是一个非常老的线程,但我想我遇到了这个问题。有没有人有任何代码显示如何遍历列表以找出要抓取的 DOM 元素? 忽略我的问题。我能够通过使用 document.getElementById('any-Control-That-Has-An-NG-Directive').scope() 来完成这项工作。 如果您使用 ng-view 并将您的视图拆分为自己的文件。您可以将和 id 放在顶部的 HTML 元素中,然后执行 document.getElementById() 那个 id。这使您可以访问该控制器的范围。方法/属性等......只是对 goosemanjack 的评论提出了一个很好的观点。【参考方案7】:

从 Angular 外部到 Angular 的互操作与调试 Angular 应用程序或与第三方库集成相同。

对于任何 DOM 元素,您都可以这样做:

angular.element(domElement).scope() 获取元素的当前作用域 angular.element(domElement).injector() 获取当前应用注入器 angular.element(domElement).controller() 获取 ng-controller 实例。

通过注入器,您可以获取 Angular 应用程序中的任何服务。同样,您可以从作用域调用任何已发布到它的方法。

请记住,对角度模型的任何更改或范围上的任何方法调用都需要包装在 $apply() 中,如下所示:

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

【讨论】:

这行得通,但我希望有某种方法可以从模块直接进入它的范围 - 这可能吗?当我已经引用了模块时,必须返回选择 [ng-app] 根节点似乎倒退了... 我无法让它工作:我正在调用angular.element(document.getElementById(divName)).scope(),但我无法从中调用任何函数,它只是在控制台中返回“未定义”。 即使我面临@Emil 上面描述的同样问题,它返回未定义。有什么帮助吗? 如果关闭调试数据,element().scope() 将不起作用,这是生产的建议。这不是让它在这种情况下无用吗?这仅用于测试/调试。 你不会希望在 Angular 1.3 中这样做。 Angular 团队不打算让我们在生产代码中的元素上调用“.scope()”。它本来是一个调试工具。因此,从 Angular 1.3 开始,您可以将其关闭。 Angular 将停止使用 jQuery 的 .data 函数将作用域附加到元素上。这将加速您的应用程序。此外,将作用域交给 jquery 的缓存功能会造成内存泄漏。所以,你绝对应该关闭它,以加快你的应用程序。 Angular 的网站有一个生产指南,您应该使用它来了解更多信息。

以上是关于从遗留代码调用 AngularJS的主要内容,如果未能解决你的问题,请参考以下文章

您如何从遗留代码上的 FxCop 开始?

Linux 5.14 从内核中清除了遗留的 IDE 代码

如何将遗留的 Dojo Toolkit 代码转换为 AMD?

如何从 javascript 调用容器管理的安全检查?

如何从 javascript 调用容器管理的安全检查?

《WebAssembly 权威指南》在浏览器中运行遗留代码