$http/$q调用上的angularjs禁用按钮

$http/$q调用上的angularjs禁用按钮,angularjs,angularjs-directive,Angularjs,Angularjs Directive,遵循DRY原则,我想编写一个按钮指令,在$http类期间保持按钮禁用 我想这样做是为了禁止用户多次单击按钮,但我无法考虑如何在指令中获取函数承诺状态,因为函数位于$scope上 该场景非常通用,单击按钮确实会调用一个函数,该函数反过来会进行$http调用。在用户单击时:按钮应该被禁用,并且应该仅在$http调用被解决后才启用,无论是成功还是失败 为什么不简单一点呢 <button ng-click="save()" ng-disabled="isProcessing">Save<

遵循DRY原则,我想编写一个按钮指令,在$http类期间保持按钮禁用

我想这样做是为了禁止用户多次单击按钮,但我无法考虑如何在指令中获取函数承诺状态,因为函数位于$scope上


该场景非常通用,单击按钮确实会调用一个函数,该函数反过来会进行$http调用。在用户单击时:按钮应该被禁用,并且应该仅在$http调用被解决后才启用,无论是成功还是失败

为什么不简单一点呢

<button ng-click="save()" ng-disabled="isProcessing">Save</button>


$scope.save = function(){
  $scope.isProcessing = true;
  $http.post('Api/Controller/Save', data).success(
    $scope.isProcessing = false;
  );
}
保存
$scope.save=函数(){
$scope.isProcessing=true;
$http.post('Api/Controller/Save',data')。成功(
$scope.isProcessing=false;
);
}
当然,如果你的应用程序中很少有地方需要这种逻辑,情况就是这样


如果这样的逻辑重复了很多次(如果你不懒惰:),那么为了遵循坚实的原则,最好将此功能包装到指令中(查看此问题的其他答案以查看此类指令的示例)

您可以在运行块中使用此选项。这将确保每当有激活的XHR调用时,所有按钮都将被禁用

myApp.run(function($rootScope, $http) {
    $http.defaults.transformRequest.push(function (data) {
        $rootScope.progress = true;
        return data;
    });
    $http.defaults.transformResponse.push(function(data){ 
        $rootScope.progress = false;
        return data;
    }) 
});
然后在你想要的任何地方使用相同的模型

 <button ng-click="save()" ng-disabled="progress">Save</button>
保存

尽管我会小心过度工程,但实现这一点的方法是使用自定义指令。本指令

<button ng-click="loadData($notify)">submit</button>
  • 接受作用域中必须返回承诺的函数的属性传递的选项
  • 单击按钮时,调用此函数并禁用该按钮
  • 在承诺的
    最后
    上,它重新启用按钮
-

这可以在按钮上使用,如下所示:

<button click-and-disable="functionThatReturnsPromise()">Click me</button>

但您可以通过调用<代码> $HTTP>代码>来替换<代码> $Time>代码,或返回任何承诺的服务的函数。

< P>您也可以考虑设置标志,并使用HTML标记字段集和NG禁用。然后,您可以根据$http调用、$timeout等来控制yourDisableFlag为true的时间

<form name="myForm">
  <fieldset ng-disabled="yourDisableFlag">
    <button id="b1" ng-click="b1function">B1</button>
    <button id="b2" ng-click="b2function">B2</button>
    <button id="b3" ng-click="b3function">B3</button>
  </fieldset>
</form>

地下一层
地下二层
地下三层

我已经准备好了覆盖默认值的指令
ngClick
指令

<button ng-click="loadData($notify)">submit</button>

您可以创建一个可用于要禁用的元素的指令

 var loadingDisabled = function ($http) {
    return {
      restrict: "a",
      link: function (scope, elem, attr) {
                scope.isLoading = function () {
                    return $http.pendingRequests.length > 0;
                }
                scope.$watch(scope.isLoading, function(v) {
                    if (v) {
                        elem.disabled = true;
                    } else {
                        elem.disabled = false;
                    }
                });

            }

    };
  };

我尝试了不同的方法,包括指令,并提出了一个简单的过滤器,将承诺转换为状态对象。然后,您可以使用status对象禁用相关按钮(或任何其他内容),并显示一些进度消息。因此,过滤代码为:

function promiseStatus() {
  return function(promise) {
    var status = {
      inProgress: true,
      resolved: false,
      rejected: false
    };
    promise
      .then(function() {
        status.resolved = true;
      })
      .catch(function() {
        status.rejected = true;
      })
      .finally(function() {
        status.inProgress = false;
      });
    return status;
  }
}
假设你有这样的控制器

function SubmitController($http) {
  var vm = this;

  vm.submitData = function() {
    return $http.post('....')
      .then(function() {
        //Doing something usefull
      });
  }
}
<button 
  ng-disabled='status.inProgress' 
  ng-click='status = (vm.submitData() | promiseStatus)'>Submit</button>
<span class='ng-hide' ng-show='status.inProgress'>Submitting...</span>
<span class='ng-hide' ng-show='status.resolved'>Submitted succsssfully!</span>
<span class='ng-hide' ng-show='status.rejected'>Failed to submit!</span>
然后模板将是这样的

function SubmitController($http) {
  var vm = this;

  vm.submitData = function() {
    return $http.post('....')
      .then(function() {
        //Doing something usefull
      });
  }
}
<button 
  ng-disabled='status.inProgress' 
  ng-click='status = (vm.submitData() | promiseStatus)'>Submit</button>
<span class='ng-hide' ng-show='status.inProgress'>Submitting...</span>
<span class='ng-hide' ng-show='status.resolved'>Submitted succsssfully!</span>
<span class='ng-hide' ng-show='status.rejected'>Failed to submit!</span>
提交
提交。。。
提交成功!
提交失败!

完整的源代码在一个

上,我喜欢@codefformer的解决方案,因为它是集中式的——即每个HTTP请求不需要额外的代码,您只需要检查HTML中的全局
进度
标志。但是,使用
transformResponse
确定请求何时完成是不可靠的,因为服务器可能不会返回任何内容;在这种情况下,不会调用处理程序,并且不会将
progress
设置回
false
。此外,如文中所述,答案不包括多个同时请求(
progress
可能在所有请求完成之前返回
false

我提出了一个类似的解决方案,它使用拦截器来解决这些问题。您可以将其放入Angular应用程序的配置功能中:

.config(function ($httpProvider) {
    $httpProvider.interceptors.push(function($q, $rootScope) {
        var numberOfHttpRequests = 0;
        return {
            request: function (config) {
                numberOfHttpRequests += 1;
                $rootScope.waitingForHttp = true;
                return config;
            },
            requestError: function (error) {
                numberOfHttpRequests -= 1;
                $rootScope.waitingForHttp = (numberOfHttpRequests !== 0);
                return $q.reject(error);
            },
            response: function (response) {
                numberOfHttpRequests -= 1;
                $rootScope.waitingForHttp = (numberOfHttpRequests !== 0);
                return response;
            },
            responseError: function (error) {
                numberOfHttpRequests -= 1;
                $rootScope.waitingForHttp = (numberOfHttpRequests !== 0);
                return $q.reject(error);
            }
        };
    });
})

现在您可以使用
waitingForHttp
禁用按钮(或显示“忙碌”页面)。使用拦截器带来了额外的好处,现在您可以使用error函数在一个位置记录所有HTTP错误(如果需要)。

此指令将禁用按钮,直到保存/承诺未实现为止。
 <button ng-click="save()" ng-disabled="progress">Save</button>
单击必须返回承诺,否则将不会禁用按钮

如果没有承诺但仍希望禁用按钮,则建议编写自己的禁用按钮逻辑

app.directive('singleClick', function ($parse) {
    return {
        compile: function ($element, attr) {
            var handler = $parse(attr.singleClick);
            return function (scope, element, attr) {
                element.on('click', function (event) {
                    scope.$apply(function () {
                        var promise = handler(scope, { $event: event }); /// calls and execute the function specified in attrs.
                        if (promise && angular.isFunction(promise.finally)) { /// will execute only if it returns any kind of promises.
                            element.attr('disabled', true);
                            promise.finally(function () {
                                element.attr('disabled', false);
                            });
                        }
                    });
                });
            };
        }
    };
});

我目前正在按照你的建议做,但如果我必须管理每个按钮一个布尔值。。创建一个指令,将在整个项目过程中自动处理该指令,我建议采用这种方式。这是唯一的方法,如果你想它是灵活的。使用拦截器侦听请求不会包括您在请求前后可能要执行的客户端操作。我看到了一些click指令,但它们都需要一个返回的承诺,这将影响控制器代码。这太不可靠了,因为您必须注意每个按钮的异步状态。公认的答案是一个更好的解决方案。这个对于简单的项目来说已经足够简单了,但是当事情变得更加复杂时,这将成为一个沉重的负担。在这种情况下,在摘要周期禁用按钮之前,可以多次单击按钮。当然,我可以这样做,但正如问题中所述,我正在寻找通用的方法。没错!我认为这可能对按钮组仍然有帮助,但您不希望每个按钮都有单独的字段集。问题是,我删除了超时功能,但我无法让它在单击后执行默认操作(例如,单击按钮后提交表单),它只是禁用按钮。这是否可能使表格也被提交?我对这个解决方案有问题。当HTTP调用返回错误代码时,按钮未重新启用@下面的codeformer解决方案在这种情况下工作得更好。我做了一点修改,不必使用隔离作用域。这允许我在同一个元素上使用translate。scope.$eval(attrs.clickAndDisable)。最后(…)并删除scope属性。想象一下,在单击按钮时,我们希望从服务器获取一些数据-这将从指令调用我们的函数,而不是从视图调用我们的函数,这是一种不好的做法。无论如何,好主意;)这是