64

注: これは AngularJS でモーダル ダイアログを表示することではありません。このトピックには多くの質問と回答があります。

この質問は、ページのモーダル ダイアログ内で [OK] と [キャンセル] の両方に対応する方法に関するものです。変数が 1 つしかないスコープがあるとします。

$scope.description = "Oh, how I love porcupines..."

ページ上にモーダル ダイアログを表示し、そのダイアログ内で ng-model="description" を使用すると、実際には、入力した説明自体にすべての変更がリアルタイムで反映されます。それは悪いことです。なぜなら、そのダイアログをキャンセルするにはどうすればよいのでしょうか?

以下で説明することを行うように言うこの質問があります。それに対する受け入れられた答えは、私が思いついたのと同じ「解決策」です:

ボタンをクリックしてモーダルを表示すると、後ろの関数に戻り、モーダルに関連するデータの一時的なコピーが作成され、モーダルがポップアップする場合の方法がわかります。次に、「OK」(または「保存」など)により、一時的な値を実際のモデル値にコピーできます。

main.js (抜粋):

$scope.descriptionUncommitted = $scope.description;

$scope.commitChanges = function () {
  $scope.description = $scope.descriptionUncommitted;
}

main.html (抜粋):

<input type="text" ng-model="descriptionUncommitted"/>

<button ng-click="commitChanges()">Save</button>

それに関する問題は、それが宣言的ではないことです! 実際、AngularJS のようなものは他にありません。必要なすべての変更を行うことができる ng-model-uncommitted="description" が必要であるかのようですが、別の宣言でトリガーしたときにのみコミットされます。どこかのプラグインにそのようなものはありますか、それともAngularJS自体が追加していますか?

編集:それを行う別の方法の例が適切であるようです。

main.js:

$scope.filename = "panorama.jpg";
$scope.description = "A panorama of the mountains.";

$scope.persist = function () { // Some function to hit a back end service. };

main.html:

<form>
  <input type="text" ng-model-uncommitted="filename"/>
  <input type="text" ng-model-uncommitted="description"/>

  <button ng-commit ng-click="persist()">Save</button>
  <button ng-discard>Cancel</button>
</form>

アイテムをグループ化する方法がわからないため、フォームタグを貼り付けたので、すべてが同じ「トランザクション」の一部であることは明らかでした(より適切な言葉がないため)。しかし、これがすべて自動的に行われ、モデル変数の複製されたコピーが初期値に使用され、入力に使用され、自動的に更新され、検証されるなどの方法が必要です。その後、最終的に破棄されるか、同じ値にコピーされます。ユーザーがコミットすることを決定した場合、最初はそれらを作成するために使用されました。

大規模な Web サイトで 20 のモーダルに対して何度も何度も同じ作業を行うには、コントローラーのコードよりもこのようなものの方が簡単ではないでしょうか? それとも私は気が狂っていますか?

4

6 に答える 6

23

基本的に、angularでは宣言的でないものがある場合は、ディレクティブを作成します。

 .directive('shadow', function() {
  return {
    scope: {
      target: '=shadow'            
    },
    link: function(scope, el, att) {
      scope[att.shadow] = angular.copy(scope.target);

      scope.commit = function() {
        scope.target = scope[att.shadow];
      };
    }
  };

それで:

  <div shadow="data">
    <input ng-model="data">
    <button ng-click="commit()">save</button>
  </div>

したがって、ディレクティブdata内にはオリジナルのコピーが入ります。そして、ボタンをクリックすると元にコピーされます。shadowdata

そして、ここに実際の例があります: jsbin

この例を超えてテストしていないため、他の場合には機能しない可能性がありますが、可能性のアイデアは得られると思います。

編集:

文字列の代わりにオブジェクトを使用した別の例と、フォーム内のいくつかのフィールド (ここでは追加angular.copyが必要です): jsbin

Edit2、角度バージョン 1.2.x

この変更によりinputディレクティブ内は分離スコープにアクセスしなくなりました。1 つの代替方法は、分離されていない子スコープ ( scope:true) を作成してデータのコピーを保持し、それを保存するために親スコープにアクセスすることです。

したがって、角度の新しいバージョンでは、これはトリックを実行するためにわずかに変更された前と同じアプローチです。

.directive('shadow', function() {
  return {
    scope: true,
    link: function(scope, el, att) {
      scope[att.shadow] = angular.copy(scope[att.shadow]);

      scope.commit = function() {
        scope.$parent[att.shadow] = angular.copy(scope[att.shadow]);
      };
    }
  };
});

例: jsbin

$parentを使用する際の問題は、最終的に途中に別のスコープがある場合に壊れる可能性があることに注意してください。

于 2013-05-30T16:04:14.847 に答える
21

Angular 1.3 の時点で、同じ動作をネイティブに実現できるngModelOptionsディレクティブがあります。

<form name="userForm">
    <input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'submit' }" name="userName">
    <button type="submit">save</button>
    <button type="button"  ng-click="userForm.userName.$rollbackViewValue();">cancel</button>
</form>

JSFiddle: http://jsfiddle.net/8btk5/104/

于 2015-03-16T12:59:34.237 に答える
11

同じ問題に直面し、このスレッドをたどると、フォームが送信されたときにのみ変更を保存するlazy-modelディレクティブを思いつきました。ng-model

使用法:

<input type="text" lazy-model="user.name">

タグにラップするように注意してください<form>。そうしないと、怠惰なモデルは変更をいつ元のモデルにプッシュするかわかりません。

完全な動作デモ: http://jsfiddle.net/8btk5/3/

lazyModel ディレクティブ コード:
( github で実際のバージョンを使用することをお勧めします)

app.directive('lazyModel', function($parse, $compile) {
  return {
    restrict: 'A',  
    require: '^form',
    scope: true,
    compile: function compile(elem, attr) {
        // getter and setter for original model
        var ngModelGet = $parse(attr.lazyModel);
        var ngModelSet = ngModelGet.assign;  
        // set ng-model to buffer in isolate scope
        elem.attr('ng-model', 'buffer');
        // remove lazy-model attribute to exclude recursion
        elem.removeAttr("lazy-model");
        return function postLink(scope, elem, attr) {
          // initialize buffer value as copy of original model 
          scope.buffer = ngModelGet(scope.$parent);
          // compile element with ng-model directive poining to buffer value   
          $compile(elem)(scope);
          // bind form submit to write back final value from buffer
          var form = elem.parent();
          while(form[0].tagName !== 'FORM') {
            form = form.parent();
          }
          form.bind('submit', function() {
            scope.$apply(function() {
                ngModelSet(scope.$parent, scope.buffer);
            });
         });
         form.bind('reset', function(e) {
            e.preventDefault();
            scope.$apply(function() {
                scope.buffer = ngModelGet(scope.$parent);
            });
         });
        };  
     }
  };
});

GitHub上の実際のソースコード

于 2013-12-17T19:29:54.583 に答える
7

あなたはこれを考えすぎているようです。プロセスは非常に単純なので、プラグインはありません。モデルの元のコピーが必要な場合は、作成してコントローラーに保持します。ユーザーがキャンセルした場合は、モデルをコピーにリセットし、FormController.$setPristine() メソッドを使用してフォームを元の状態に戻します。

//Controller:

myService.findOne({$route.current.params['id']}, function(results) {
    $scope.myModel = results;
    var backup = results;
}

//cancel
$scope.cancel = function() {
    $scope.myModel = backup;
    $scope.myForm.$setPristine();
}

次に、あなたの見解で:

<form name="myForm">

$scope.myForm コントローラーを作成するには、フォームに名前を付ける必要があります。

于 2013-05-30T14:59:50.690 に答える
0

これは、フォームタグやその他のものに依存せず、宣言型にすることで、シンプルに保つための私の試みです。

簡単なディレクティブ:

.directive("myDirective", function(){
return {
  scope: {
    item: "=myDirective"
  },
  link: function($scope){
    $scope.stateEnum = {
      view: 0, 
      edit: 1
    };

    $scope.state = $scope.stateEnum.view;

    $scope.edit = function(){
      $scope.tmp1 = $scope.item.text;
      $scope.tmp2 = $scope.item.description;
      $scope.state = $scope.stateEnum.edit;
    };

    $scope.save = function(){
      $scope.item.text = $scope.tmp1;
      $scope.item.description = $scope.tmp2;
      $scope.state = $scope.stateEnum.view;
    };

    $scope.cancel = function(){
      $scope.state = $scope.stateEnum.view;
    };
  },
  templateUrl: "viewTemplate.html"
};
})

viewTemplate.html:

<div>
  <span ng-show="state == stateEnum.view" ng-click="edit()">{{item.text}}, {{item.description}}</span>
  <div ng-show="state == stateEnum.edit"><input ng-model="tmp1" type="text"/> <input ng-model="tmp2" type="text"/><a href="javascript:void(0)" ng-click="save()">save</a> <a href="javascript:void(0)" ng-click="cancel()">cancel</a></div>
</div>

次に、コンテキスト (項目) を設定します。

<div ng-repeat="item in myItems">
  <div my-directive="item"></div>
</div>

実際に見てみましょう: http://plnkr.co/edit/VqoKQoIyhtYnge2hzrFk?p=preview

于 2014-05-05T14:29:52.950 に答える