レガシーコードから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の外部からサービスを更新できますか?コントローラはサービスからのイベントをリッスンできますか?1つの実験(フィドルはクリック)で、サービスにアクセスできるように見えましたが、サービスのデータを更新してもビューに反映されません(例では、<option>をに追加する必要があります<select>)。

ありがとう!


1
上記のjsfiddleでは、を使用してアプリ内の要素をターゲットにすることなく、インジェクターが取得されvar injector = angular.injector(['ng', 'MyApp']);ます。これを行うと、完全に新しいコンテキストと複製が得られますmyService。つまり、サービスとモデルの2つのインスタンスが作成され、間違った場所にデータが追加されることになります。代わりに、を使用してアプリ内の要素をターゲットにする必要がありますangular.element('#ng-app').injector(['ng', 'MyApp'])。この時点で、$ applyを使用してモデルの変更をラップできます。
Thanh Nguyen

回答:


293

Angularの外側からAngularへの相互運用は、Angleアプリケーションのデバッグやサードパーティライブラリとの統合と同じです。

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
element()。scope()は、デバッグデータがオフになっている場合は機能しません。これは、本番環境での推奨事項です。このシナリオではそれは役に立たないのではないですか?これはテスト/デバッグ専用です。
K.ノーベルト

4
Angular 1.3ではこれをしたくないでしょう。Angularチームは、プロダクションコードの要素で「.scope()」を呼び出すつもりはありませんでした。これはデバッグツールであることを意味していました。したがって、Angular 1.3以降では、これをオフにできます。Angularは、jQueryの.data関数を使用した要素へのスコープのアタッチを停止します。これにより、アプリの速度が向上します。さらに、スコープをjqueryのキャッシュ機能に渡すと、メモリリークが発生します。したがって、アプリを高速化するには、これを確実にオフにする必要があります。Angularのサイトには、詳細を学ぶために使用すべき制作ガイドがあります。
凍えるような

86

ミスコは(明らかに)正しい答えを出しましたが、私たちの初心者の一部はそれをさらに簡略化する必要があるかもしれません。

レガシーアプリ内からAngularJSコードを呼び出す場合、AngularJSコードはレガシーアプリケーションの保護されたコンテナー内に存在する「マイクロアプリ」と考えてください。(非常に正当な理由で)直接呼び出すことはできませんが、$ scopeオブジェクトを介してリモート呼び出しを行うことができます。

$ scopeオブジェクトを使用するには、$ scopeのハンドルを取得する必要があります。幸いなことに、これは非常に簡単です。

AngularJS "micro-app" HTML内の任意のHTML要素のIDを使用して、AngularJSアプリ$ scopeのハンドルを取得できます。

例として、AngularJSコントローラー内でsayHi()やsayBye()などのいくつかの関数を呼び出したいとしましょう。AngularJS HTML(ビュー)には、「MySuperAwesomeApp」というIDの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


4
リンクのみの回答は推奨されないことに注意してください。SOの回答は、ソリューションの検索のエンドポイントである必要があります。参照としてリンクを維持しながら、ここにスタンドアロンの概要を追加することを検討してください。
クレオパトラ2013

追加の明確な説明。ありがとう。
Joe

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 2013

私はこれが本当に古いスレッドであることを知っていますが、私はこの問題に遭遇していると思います。誰かがリストを調べて、取得する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 ....

フォームスキーマサービスは、予想どおりに角度インジェクターを使用して作成されます。

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(); すると、スコープを変更するとアプリが壊れる可能性があります。しかし、もしあなたのアプリがスコープについての知識を持っていて、それが頻繁に変更されないことに依存できるなら、以前に提案されたことは実行可能なアプローチです。

お役に立てれば。フィードバックも歓迎します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.