【问题标题】:Is there a pattern for dealing with "Cancel" in AngularJS modal dialogs?AngularJS模式对话框中是否有处理“取消”的模式?
【发布时间】:2013-05-26 03:29:23
【问题描述】:

注意:这不是关于使用 AngularJS 显示模态对话框,该主题有很多问题和答案!

这个问题是关于如何在页面上的模式对话框中对 OK 和 Cancel 做出反应。假设你有一个只有一个变量的作用域:

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

如果我在页面上为您提供一个模式对话框并在该对话框中使用 ng-model="description",那么您所做的所有更改实际上都是在您键入时对描述本身实时进行的。这很糟糕,因为那你如何取消该对话框?

有这个问题说按照我在下面解释的去做。公认的答案与我想出的“解决方案”相同:AngularJS: Data-bound modal - save changes only when "Save" is clicked, or forget changes if "Cancel" is clicked

如果单击按钮以调出模态框返回到后面的函数并为模态框创建相关数据的临时副本然后弹出模态框,我可以看到该怎么做。然后“确定”(或“保存”或其他)可以将临时值复制到实际模型值。

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>

我在它周围贴了一个表单标签,因为我不知道你会如何对这些项目进行分组,所以很明显它都是同一个“交易”的一部分(因为没有更好的词)。但是需要某种方式使这一切都可以自动发生,并且模型变量的克隆副本用于初始值,用于输入并自动更新,验证等,然后最终丢弃或复制到相同的值如果用户决定提交,最初用于创建它们。

这样的事情不是比控制器中的代码更容易为大型网站中的 20 个模式一遍又一遍地完成这项工作吗?还是我疯了?

【问题讨论】:

  • 您是否正在寻找一个取消按钮,该按钮会自动恢复对模态模型所做的更改?单击取消按钮时如何刷新模型,以便在不需要额外临时变量的情况下覆盖模型?你能提供一个你正在寻找的语法/标记的例子吗?
  • 您不能允许更改发生,因为用户可能会在页面上看到 {{description}} 的实例在他们键入时更新(或在号等)。我将在上面添加一些想法,以提供我希望看到的内容。我只是希望某些东西已经存在。
  • 你绝对不是疯子。我不清楚描述和altDescription。它们是两个独立的模型,而不是您的备份版本吗?在您的示例中,这些字段会显示什么?已编辑或未编辑的数据?
  • 再考虑一下,我很确定你可以在指令中完成你想要的。
  • 我试图更清楚地表明这些值是不相关的,我只是想表明即使有多个模型值,这也应该有效。我希望您会调出该页面,并且您会看到输入中填充了文件名和描述的当前值。编辑它们后,如果用户点击“保存”,这些更改将被推送到模型变量中,就像您对每个变量都使用了 ng-model 一样,如果需要,ng-click 可以触发一些东西将其推送到后端服务.如果用户点击取消,那么这些更改将被丢弃,并且模型值永远不会改变。

标签: javascript angularjs


【解决方案1】:

另一种方法是在编辑之前复制模型,然后在取消时恢复原始模型。角度控制器代码:

//on edit, make a copy of the original model and store it on scope
function edit(model){
  //put model on scope for the cancel method to access
  $scope.modelBeingEdited = model;
  //copy from model -> scope.originalModel
  angular.copy(model,$scope.originalModel);  
}

function cancelEdit(){
  //copy from scope.original back to your model 
  angular.copy($scope.originalModel, $scope.modelBeingEdited)  
}
因此,在打开模态对话框时,调用编辑函数并将指针传递给您要编辑的模型。 然后确保您的模态对话框绑定到 $scope.editingModel。 在取消时,调用取消函数,它会将原始值复制回来。

希望对某人有所帮助!

【讨论】:

  • 请参阅上面的 sharondios 回答以获得此概念的更好版本 - 因为您不需要将原始模型存储在示波器上。
【解决方案2】:

从 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/

【讨论】:

  • 提交按钮在 ng-form 中不起作用,那么我如何告诉模型仅在按下特定按钮(不是提交)时才更新?
  • Submit 确实适用于 ng-form,只需在 form 标签中添加一个 ng-submit="..." 属性,当点击提交按钮时,它就会被执行。但是,此解决方案的一个问题是您无法进行实时验证,因为在提交表单之前不会提交更改,因此不会触发验证。
  • 我认为这是最好的答案。不要使用所有那些“复制”。只需学习如何正确使用ng-model
  • 值得注意的是,如果有多个字段,您也可以直接在表单上使用ng-model-options$rollbackViewValue()
  • 这很好。但是,请记住,点击提交后模型会更新。如果您将数据传输到服务器并且请求返回错误,则会出现问题。用户现在可能想取消,但您不能回滚视图值。如果有人对此问题有任何想法,请告诉我。在那之前,我将不得不坚持复制。 :-(
【解决方案3】:

基本上,如果某些东西不是声明性的,则在角度中,您会创建一个指令

 .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>

所以shadow 指令中的data 将是原始data副本。 当点击按钮时,它会被复制回原来的状态。

这里是工作示例:jsbin

我没有在这个例子之外测试过它,所以它在其他情况下可能不起作用,但我认为它给出了可能性的想法。

编辑:

另一个使用对象而不是字符串的示例,以及表单中的多个字段(此处需要额外的angular.copy):jsbin

Edit2,角度版本 1.2.x

根据change, 指令内的input 不再访问隔离范围。一种替代方法是创建一个非隔离的子范围 (scope:true),以保存数据的副本并访问父范围以保存它。

因此,对于更高版本的 angular,这与之前的方法相同,稍作修改即可:

.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 的问题在于,如果最终中间有另一个作用域,它可能会中断。

【讨论】:

  • 这很有趣。我还没有完成我的第一个指令,所以这将是一个开始的绝佳机会。
  • 我真的很喜欢这种在模态表单上的各个字段上进行保存的方法。但是当“ng-modal”和“shadow”属性的表达式不仅仅是简单的标量值时,我遇到了一个问题。您如何修改指令以使用分层范围内的数据?我将范围设置为具有许多嵌套属性和数组的对象,并希望在该层次结构中的各种嵌套属性中使用此指令。但当前指令采用标量值。
  • @JezzSantos 它应该与对象一起使用,只需在复制回原始范围时添加一个额外的angular.copy(以避免持有对同一对象的引用)。这是example
  • 为什么它只适用于 Angular 1.0.5,如果您更改为更高版本,父范围会立即更改
  • @RuiGonçalves 没错,关于隔离作用域的一些东西在新版本中发生了变化(也许是this?)。我已经用另一种选择更新了答案,我不太喜欢 $parent 的东西,但至少可以。
【解决方案4】:

这是我保持简单的尝试,使其具有声明性并且不依赖于表单标签或其他内容。

一个简单的指令:

.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>

然后设置上下文(item):

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

查看实际操作:http://plnkr.co/edit/VqoKQoIyhtYnge2hzrFk?p=preview

【讨论】:

    【解决方案5】:

    面对同样的问题,通过这个帖子我想出了lazy-model 指令,它的工作原理与ng-model 完全相同,但仅在提交表单时保存更改

    用法:

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

    请注意将其包装到&lt;form&gt; 标签中,否则惰性模型将不知道何时将更改推送到原始模型。

    完整的工作演示http://jsfiddle.net/8btk5/3/

    lazyModel 指令代码:
    (最好使用actual version on 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);
                });
             });
            };  
         }
      };
    });
    

    Actual source code on GitHub

    【讨论】:

      【解决方案6】:

      你似乎想多了。没有插件,因为过程非常简单。如果您想要模型的原始副本,请制作一个并将其保存在控制器中。如果用户取消,请将模型重置为您的副本并使用 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 控制器。

      【讨论】:

      • 这正是我所说的我不想做的事情,因为它与声明性解决方案相反。这是我们目前必须做的,但它让控制器在 HTML/UI 面前站起来,而不是简单地充当业务逻辑和模型的客户端副本的守护者。
      • 我没有遵循您的“控制器全部在脸上”部分。副本保留在控制器中。它没有触及视图,只是重置模型,与首先设置它没有什么不同。你会设想在每个 ng-model 字段上都有一个指令吗?或者你可能认为 Angular 可以自己保留模型的影子副本?第二种方法听起来很有趣。
      • 好的,看看我添加的例子,看看是否更有意义。
      • $setPristine() 的价值/目的是什么?这有什么帮助?你知道是否有办法在 Angular 1.3.0 中使用新方法 $rollbackViewValue() 来避免存储副本?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-30
      • 1970-01-01
      相关资源
      最近更新 更多