从旧版代码调用AngularJS


180

我正在使用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外部更新服务吗?控制器可以侦听来自服务的事件吗?在一个实验中(单击以拨弄小提琴),我确实可以访问服务,但更新服务的数据未反映在视图中(在示例中<option>应将添加到<select>)。

谢谢!


1
请注意,在上方的jsfiddle中,无需使用定位应用程序内的元素即可获得注入器var injector = angular.injector(['ng', 'MyApp']);。这样做将为您提供全新的上下文和重复的上下文myService。这意味着您将获得服务和模型的两个实例,并将数据添加到错误的位置。您应该改为使用定位应用程序中的元素angular.element('#ng-app').injector(['ng', 'MyApp'])。此时,您可以使用$ apply包装模型更改。
Thanh Nguyen

Answers:


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
这可行,但是我希望有某种方法可以直接从Module到达其作用域-可能吗?[ng-app]当我已经引用了Module时,不得不回头选择根节点似乎倒退了……
mindplay.dk

5
我无法使用它:我正在调用angular.element(document.getElementById(divName)).scope(),但是我无法从中调用任何函数,它只是在控制台中返回“未定义”。
埃米尔(Emil)

1
即使我遇到了@Emil所述的相同问题,它也返回未定义。有什么帮助吗?
Bibin 2013年

5
如果关闭调试数据,element()。scope()将不起作用,这是生产建议。这不是在这种情况下使它无用吗?这仅用于测试/调试。
K. Norbert 2014年

4
您将不想在Angular 1.3中执行此操作。Angular团队无意让我们在生产代码中的元素上调用“ .scope()”。它原本是一个调试工具。因此,从Angular 1.3开始,您可以将其关闭。Angular将停止使用jQuery的.data函数将范围附加到元素。这将加速您的应用程序。另外,将范围交给jquery的缓存功能会造成内存泄漏。因此,您一定要关闭此功能,以加快应用程序的速度。Angular的站点上有一个生产指南,您应该使用它来了解更多信息。
2014年

86

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小组的幻灯片演示中展示了这一点(只需跳到最后两张幻灯片)

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

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

忽略我的问题。我仅使用document.getElementById('any-Control-That-Has-An-NG-Directive')。scope()就能完成这项工作。
JerryKur 2014年

如果您使用ng-view并将视图拆分为自己的文件。您可以将放在id顶部HTML元素中,然后执行document.getElementById()该ID。这使您可以访问该控制器的范围。方法/属性等。。。只是对goosemanjack的评论进行了说明。
2015年

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

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

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>

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


3

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

在大多数情况下,如果需要外部旧版代码与UI的状态或应用程序的内部功能进行交互,则可以使用服务来抽象化那些更改。如果外部代码直接与您的角度控制器,组件或指令交互,则您的应用程序与旧代码之间的耦合很大,这是个坏消息。

我最终使用的是浏览器可访问的全局变量(即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.module('myApp.services').
service('FormSchemaService', ['$window' , FormSchemaService ])

在我的控制器中:function(){'use strict';

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

希望这可以帮助。也欢迎任何反馈。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.