web-dev-qa-db-ja.com

AngularJS 1.5+コンポーネントはウォッチャーをサポートしていませんが、回避策は何ですか?

カスタムディレクティブを新しい コンポーネントアーキテクチャ にアップグレードしています。コンポーネントはウォッチャーをサポートしていません。これは正しいです?その場合、オブジェクトの変更をどのように検出しますか?基本的な例として、ゲームにバインドされた子コンポーネントゲームを持つカスタムコンポーネントmyBoxがあります。ゲームコンポーネント内に変更ゲームがある場合、myBox内にアラートメッセージを表示するにはどうすればよいですか? rxJSメソッドがあることを理解していますが、これを純粋に角度で行うことは可能ですか?私 JSFiddle

JavaScript

var app = angular.module('myApp', []);
app.controller('mainCtrl', function($scope) {

   $scope.name = "Tony Danza";

});

app.component("myBox",  {
      bindings: {},
      controller: function($element) {
        var myBox = this;
        myBox.game = 'World Of warcraft';
        //IF myBox.game changes, show alert message 'NAME CHANGE'
      },
      controllerAs: 'myBox',
      templateUrl: "/template",
      transclude: true
})
app.component("game",  {
      bindings: {game:'='},
      controller: function($element) {
        var game = this;


      },
      controllerAs: 'game',
      templateUrl: "/template2"
})

HTML

<div ng-app="myApp" ng-controller="mainCtrl">
  <script type="text/ng-template" id="/template">
    <div style='width:40%;border:2px solid black;background-color:yellow'>
      Your Favourite game is: {{myBox.game}}
      <game game='myBox.game'></game>
    </div>
  </script>

 <script type="text/ng-template" id="/template2">
    <div>
    </br>
        Change Game
      <textarea ng-model='game.game'></textarea>
    </div>
  </script>

  Hi {{name}}
  <my-box>

  </my-box>

</div><!--end app-->
76
Ka Tech

Watchers なしでコンポーネントを書く

この回答では、 watchers。 を使用せずにAngularJS 1.5コンポーネントを作成するために使用する5つのテクニックの概要を説明します


ng-changeディレクティブを使用します

angularJs2の準備でwatchを使用せずにobjの状態変化を観察するために利用できるaltメソッドは何ですか?

ng-changeディレクティブを使用して、入力の変更に対応できます。

<textarea ng-model='game.game' 
          ng-change="game.textChange(game.game)">
</textarea>

また、イベントを親コンポーネントに伝播するには、イベントハンドラーを子コンポーネントの属性として追加する必要があります。

<game game='myBox.game' game-change='myBox.gameChange($value)'></game>

JS

app.component("game",  {
      bindings: {game:'=',
                 gameChange: '&'},
      controller: function() {
        var game = this;
        game.textChange = function (value) {
            game.gameChange({$value: value});
        });

      },
      controllerAs: 'game',
      templateUrl: "/template2"
});

そして、親コンポーネントで:

myBox.gameChange = function(newValue) {
    console.log(newValue);
});

これは今後推奨される方法です。 $watchを使用するAngularJS戦略は、ポーリング戦略であるためスケーラブルではありません。 $watchリスナーの数が2000年頃になると、UIが遅くなります。 Angular 2の戦略は、フレームワークをより反応的にし、$watch$scopeに配置しないようにすることです。


$onChangesライフサイクルフックを使用する

バージョン1.5.3では、AngularJSは$onChangesライフサイクルフックを$compileサービスに追加しました。

ドキュメントから:

コントローラーは、ライフサイクルフックとして機能する次のメソッドを提供できます。

  • $ onChanges(changesObj)-一方向(<)または補間(@)バインディングが更新されるたびに呼び出されます。 changesObjは、キーが変更されたバインドされたプロパティの名前であるハッシュであり、値は{ currentValue: ..., previousValue: ... }という形式のオブジェクトです。このフックを使用して、バインドされた値のクローンを作成するなど、コンポーネント内で更新をトリガーし、外部値の偶発的な変更を防ぎます。

— AngularJS包括的なディレクティブAPIリファレンス-ライフサイクルフック

$onChangesフックは、<一方向バインディングを使用して、コンポーネントへの外部変更に対応するために使用されます。 ng-changeディレクティブは、ng-modelバインディングを使用して、コンポーネントの外部の&コントローラーからの変更を伝達するために使用されます。


$doCheckライフサイクルフックを使用する

version 1.5.8で、AngularJSは$doCheckライフサイクルフックを$compileサービスに追加しました。

ドキュメントから:

コントローラーは、ライフサイクルフックとして機能する次のメソッドを提供できます。

  • $doCheck()-ダイジェストサイクルの各ターンで呼び出されます。変更を検出して対処する機会を提供します。検出した変更に応じて実行するアクションは、このフックから呼び出す必要があります。これを実装しても、$onChangesが呼び出されるタイミングには影響しません。たとえば、このフックは、Angularの変更ディテクターによって検出されない変更を$onChangesでトリガーしない、深い等価チェックを実行する場合、またはDateオブジェクトをチェックする場合に役立ちます。このフックは引数なしで呼び出されます。変更を検出する場合、現在の値と比較するために以前の値を保存する必要があります。

— AngularJS包括的なディレクティブAPIリファレンス-ライフサイクルフック


requireとのコンポーネント間通信

ディレクティブは 必須 他のディレクティブのコントローラーを使用して、相互間の通信を可能にします。これは、コンポーネントで require プロパティのオブジェクトマッピングを提供することで実現できます。オブジェクトキーは、必要なコントローラー(オブジェクト値)が必要なコンポーネントのコントローラーにバインドされるプロパティ名を指定します。

app.component('myPane', {
  transclude: true,
  require: {
    tabsCtrl: '^myTabs'
  },
  bindings: {
    title: '@'
  },
  controller: function() {
    this.$onInit = function() {
      this.tabsCtrl.addPane(this);
      console.log(this);
    };
  },
  templateUrl: 'my-pane.html'
});

詳細については、「 AngularJS開発者ガイド-コンポーネント間通信 」を参照してください


RxJS を使用してサービスから値をプッシュする

たとえば、状態を保持しているサービスがある状況ではどうでしょうか。そのサービスに変更をプッシュし、ページ上の他のランダムなコンポーネントがそのような変更を認識する方法はありますか?最近この問題への取り組みに苦労しています

RxJS Extensions for Angular でサービスを構築します。

<script src="//unpkg.com/angular/angular.js"></script>
<script src="//unpkg.com/rx/dist/rx.all.js"></script>
<script src="//unpkg.com/rx-angular/dist/rx.angular.js"></script>
var app = angular.module('myApp', ['rx']);

app.factory("DataService", function(rx) {
  var subject = new rx.Subject(); 
  var data = "Initial";

  return {
      set: function set(d){
        data = d;
        subject.onNext(d);
      },
      get: function get() {
        return data;
      },
      subscribe: function (o) {
         return subject.subscribe(o);
      }
  };
});

次に、変更をサブスクライブします。

app.controller('displayCtrl', function(DataService) {
  var $ctrl = this;

  $ctrl.data = DataService.get();
  var subscription = DataService.subscribe(function onNext(d) {
      $ctrl.data = d;
  });

  this.$onDestroy = function() {
      subscription.dispose();
  };
});

クライアントはDataService.subscribeで変更をサブスクライブでき、プロデューサーはDataService.setで変更をプッシュできます。

PLNKRのデモ

149
georgeawg

$watchオブジェクトは$scopeオブジェクト内で使用できるため、コントローラーファクトリ関数内に$scopeを追加してから、変数にウォッチャーを配置する必要があります。

$scope.$watch(function(){
    return myBox.game;
}, function(newVal){
   alert('Value changed to '+ newVal)
});

ここでデモ

注:$scopeの依存関係を削除して、Angular2に一歩近づくためにdirectivecomponentに変換したことを知っています。しかし、このケースでは削除されなかったようです。

更新

基本的にangular 1.5は.componentメソッドを追加して、2つの異なる機能を区別しています。 component。standsと同様に、selectorを追加して特定の動作を実行します。ここで、directiveはDOMに特定の動作を追加することを意味します。ディレクティブは、.directive DDO(ディレクティブ定義オブジェクト)の単なるラッパーメソッドです。表示されるのは、angularコンパイル済みDOMを取得できるlink/compileメソッドを使用しているときに.component関数を削除したことだけです。

Angularコンポーネントライフサイクルフックの$onChanges/$doCheckライフサイクルフックを使用してください。これらはAngular 1.5.3+バージョン以降で使用可能になります。

$ onChanges(changesObj)-バインディングが更新されるたびに呼び出されます。 changesObjは、キーがバインドされたプロパティの名前であるハッシュです。

$ doCheck()-バインドの変更時にダイジェストサイクルの各ターンで呼び出されます。変更を検出して対処する機会を提供します。

コンポーネント内で同じ関数を使用することにより、コードがAngularに移行するための互換性を確保します2。

8
Pankaj Parkar

私のソリューションに興味のある人には、RXJS Observablesに頼ることになります。これは、Angularに到達するときに使用しなければならないものです。何を見ますか。

JS FIDDLE RXJS Observables

class BoxCtrl {
    constructor(msgService) {
    this.msgService = msgService
    this.msg = ''

    this.subscription = msgService.subscribe((obj) => {
      console.log('Subscribed')
      this.msg = obj
    })
    }

  unsubscribe() {
    console.log('Unsubscribed')
    msgService.usubscribe(this.subscription)
  }
}

var app = angular
  .module('app', ['ngMaterial'])
  .controller('MainCtrl', ($scope, msgService) => {
    $scope.name = "Observer App Example";
    $scope.msg = 'Message';
    $scope.broadcast = function() {
      msgService.broadcast($scope.msg);
    }
  })
  .component("box", {
    bindings: {},
    controller: 'BoxCtrl',
    template: `Listener: </br>
    <strong>{{$ctrl.msg}}</strong></br>
    <md-button ng-click='$ctrl.unsubscribe()' class='md-warn'>Unsubscribe A</md-button>`
  })
  .factory('msgService', ['$http', function($http) {
    var subject$ = new Rx.ReplaySubject();
    return {
      subscribe: function(subscription) {
        return subject$.subscribe(subscription);
      },
      usubscribe: function(subscription) {
        subscription.dispose();
      },
      broadcast: function(msg) {
        console.log('success');
        subject$.onNext(msg);
      }
    }
  }])
4
Ka Tech

angular 1.5コンポーネントとともに、受け入れられた回答で推奨されているng-changeの使用に関する小さなヘッズアップ。

ng-modelおよびng-changeが機能しないコンポーネントを監視する必要がある場合は、次のようにパラメーターを渡すことができます。

コンポーネントが使用されるマークアップ:

<my-component on-change="$ctrl.doSth()"
              field-value="$ctrl.valueToWatch">
</my-component>

コンポーネントjs:

angular
  .module('myComponent')
  .component('myComponent', {
    bindings: {
      onChange: '&',
      fieldValue: '='
    }
  });

コンポーネントのマークアップ:

<select ng-model="$ctrl.fieldValue"
        ng-change="$ctrl.onChange()">
</select>
2
Wtower

遅刻だ。しかし、それは他の人々に役立ちます。

app.component("headerComponent", {
    templateUrl: "templates/header/view.html",
    controller: ["$rootScope", function ($rootScope) {
        let $ctrl = this;
        $rootScope.$watch(() => {
            return $ctrl.val;
        }, function (newVal, oldVal) {
            // do something
        });
    }]
});
0
tetra master

IE11で利用可能なMutationObserver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver 。 DOM /コントローラーの分離を半壊するコントローラーに$ elementサービスを注入する必要がありますが、これは、angularjsの基本的な例外(つまり、欠陥)であると感じています。 hide/showは非同期であるため、angularjsとangular-bootstrap-tabが提供しないon-showコールバックが必要です。また、どの特定のDOM要素uが観察したいかを知る必要があります。 Anglejsコントローラーの次のコードを使用して、ショーでHighchartsチャートリフローをトリガーしました。

const myObserver = new MutationObserver(function (mutations) {
    const isVisible = $element.is(':visible') // Requires jquery
    if (!_.isEqual(isVisible, $element._prevIsVisible)) { // Lodash
        if (isVisible) {
            $scope.$broadcast('onReflowChart')
        }
        $element._prevIsVisible = isVisible
    }
})
myObserver.observe($element[0], {
    attributes: true,
    attributeFilter: ['class']
})
0
user982671

本当にナイスな回答を受け入れましたが、イベントのパワーも使用できると付け加えるかもしれません(もしそうなら、Qtシグナル/スロットのように)。

イベントはブロードキャストされます:$rootScope.$broadcast("clickRow", rowId)は、親(または子コントローラー)によっても行われます。次に、コントローラーで次のようにイベントを処理できます。

$scope.$on("clickRow", function(event, data){
    // do a refresh of the view with data == rowId
});

このようなログを追加することもできます(ここから取得: https://stackoverflow.com/a/34903433/3147071

var withLogEvent = true; // set to false to avoid events logs
app.config(function($provide) {
    if (withLogEvent)
    {
      $provide.decorator("$rootScope", function($delegate) {
        var Scope = $delegate.constructor;
        var origBroadcast = Scope.prototype.$broadcast;
        var origEmit = Scope.prototype.$emit;

        Scope.prototype.$broadcast = function() {
          console.log("$broadcast was called on $scope " + this.$id + " with arguments:",
                     arguments);
          return origBroadcast.apply(this, arguments);
        };
        Scope.prototype.$emit = function() {
          console.log("$emit was called on $scope " + this.$id + " with arguments:",
                     arguments);
          return origEmit.apply(this, arguments);
        };
        return $delegate;
      });
    }
});
0
sebius