【问题标题】:Change in value of scope variable is not getting reflected in my string范围变量值的变化没有反映在我的字符串中
【发布时间】:2017-09-05 12:39:16
【问题描述】:

我有以下字符串:

"/root/get";

现在我在上面的字符串中生成一个带有 1 个范围变量的查询字符串,但问题是当该变量的值发生变化时,新值不会自动在我的 URL 中更新。

您可以在下面的演示中看到我有 2 个按钮更新和检查。在更新中,我正在生成查询字符串,在检查按钮上我正在更新范围变量的值,但这并没有反映在我的 URL 中。

我不明白为什么会这样。

单击检查按钮而不调用 generateQueryParameters 方法时的预期输出:

/root/get?no=2

var app = angular.module("myApp", []);
        app.controller("myController", function ($scope) {
        
        $scope.no = 1;
        $scope.str = "/root/get";
     
        $scope.update = function (data) {
          $scope.str=generateQueryParameters($scope.str,
               "no",$scope.no);
               console.log($scope.str);
        };
           

            $scope.check = function () {
              $scope.no=2;
              console.log($scope.str);
            };
               
    function generateQueryParameters(url,name,value)
    {
        var re = new RegExp("([?&]" + name + "=)[^&]+", "");
        function add(sep) {
            url += sep + name + "=" + encodeURIComponent(value);
        }
        function change() {
            url = url.replace(re, "$1" + encodeURIComponent(value));
        }
        if (url.indexOf("?") === -1) {
            add("?");
        } else {
            if (re.test(url)) {
                change();
            } else {
                add("&");
            }
        }
        return url;
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController">
    <input type="button" value="Update" ng-click="update()">
    <input type="button" value="Check" ng-click="check()">
</div>

更新:我知道 $scope.no 的值何时会改变,所以我认为我不需要 watcher 并且我不想再次调用 generateParameters。为什么 angular 不是像 Angular 绑定一样自动更新我的 str 中的值?

【问题讨论】:

  • 你试过观察范围变量吗?

标签: javascript angularjs angularjs-scope angularjs-bindings


【解决方案1】:

让我们从代码中的问题开始。你通过一个函数生成请求字符串,这个函数实际上只在update函数内部调用

$scope.update = function(data) {
  $scope.str = generateQueryParameters($scope.str,
    "no", $scope.no);
  console.log($scope.str);
};


$scope.check = function() {
  $scope.no = 2;
  console.log($scope.str);
};

您在check 函数中更改了版本号,但没有再次调用该函数以更改$scope.str,因此$scope.str 的值仍然相同。

您可以在 sn-p 中执行以下步骤来轻松测试:

  1. 点击更新(v0)

  2. 点击检查 (v0)

  3. 然后再次点击更新,你会看到它现在已经更新(v2)

因此,在第 2 步中,您实际上更改了版本,您根本不会再次生成要使用的 str

您的代码很容易修复,只需安排您的代码,以便在每次更改版本时调用分配新str 的函数:

$scope.update = function(data) {
  $scope.no = 1;
  $scope.str = generateQueryParameters($scope.str,
    "no", $scope.no);
  console.log($scope.str);
};


$scope.check = function() {
  $scope.no = 2;
  // here you need to call again your function for composing your URL and changing the value of str
  $scope.str = generateQueryParameters($scope.str,
    "no", $scope.no);
  console.log($scope.str);
};

function generateStringRequest() {

}

否则需要关注no版本参数,每次版本变化时自动刷新str值。但是这个解决方案意味着您将在调用 API 的每个控制器中都有一个观察者,并且所有参数总是硬编码在控制器内部:

$scope.$watch('no', function() {
    $scope.str = generateQueryParameters($scope.str, 'no', $scope.no);
});

即使这个解决方案有效,实际上也很糟糕。 管理调用的逻辑在控制器内部,这是一种非常糟糕的做法(您应该考虑为此使用集中式服务)。

更好的是,在 AngularJS 中,您可以使用自定义拦截器并在那里管理所有要执行的有关 HTTP 请求的操作。 因此,您可以将要使用的 API 版本指定为 HTTP 请求的参数。

这将使您的代码保持干净。此外,如果您想在将来更改某些请求,您可以简单地更改该请求参数。 如果你想改变所有的请求,在拦截器中你可以简单地设置所有对version 1的请求都将被version 2替换。

这里是如何定义拦截器的示例代码:

angular.module('myApp').factory('MyHTTPFactory', MyHTTPFactory)
  .config(function($httpProvider) {
    // register the new interceptor in AngularJS
    $httpProvider.interceptors.push('MyHTTPFactory');
  });

MyHTTPFactory.$inject = [];

function MyHTTPFactory() {

  // this is the base URL of your rest requests, so this interceptor will be applied only to the requests directed to your service
  const MY_REST_URL = 'blablabla';

  // methods exposed by the service
  let factory = {
    request: request,
    requestError: requestError,
    response: response,
    responseError: responseError
  };
  return factory;

  // ============================================================


  function request(config) {
    if (config.url.includes(MY_REST_URL)) {
      let versionToUse = config.version;
      // here use a function for elaborating your query string, directly in the interecptor code
      config.url = elaborateQueryString();
    }
    return config;
  }

  function requestError(config) {
    return config;
  }

  function response(res) {
    return res;
  }

  function responseError(res) {
    return res;
  }

  // function for getting the query string you want to
  function elaborateQueryString() {
    // elaborate your requests
  }

}

然后像往常一样通过$http在请求中添加您要使用的版本作为参数执行HTTP请求:

// perform your request as usual simply specifying the new version parameter
let request = {
  url: `myserviceurl`,
  version: '2' // or whatever you qNR
};
$http(request);

因此拦截器会在执行之前“嗅探”您的所有请求,它会根据您的需要正确组合版本和查询字符串,并且您的所有操作都在其中进行管理和集中管理。

就像最后一个提示一样,在定义诸如版本号、rest 服务的端点等常量时,请使用 AngularJS constant 并将它们注入到您需要使用它们的地方。硬编码字符串不是一个好习惯。

【讨论】:

  • 赞成您为帮助我所做的努力以及如此简短的回答:)
【解决方案2】:

您可以使用新的 controller as 语法(允许您直接绑定到控制器属性和方法)轻松定义 property str,如下所示:

  Object.defineProperty(vm,
    "str", {
      get: function() {
        return vm.generateQueryParameters("/root/get","no", vm.no);
      },
      set: function(newValue) {
        // setter function
      },
      enumerable: true,
      configurable: true
  });

这意味着每次您访问str 的值时,它都会重新评估url 字符串。这使您的代码独立独立于$scope$watch,并且更具前瞻性。

var app = angular.module("myApp", []);
app.controller("myController", function() {
  var vm = this;
  vm.no = 1;

  Object.defineProperty(vm,
    "str", {
      get: function() {
        return vm.generateQueryParameters("/root/get","no", vm.no);
      },
      set: function(newValue) {
        // setter function
      },
      enumerable: true,
      configurable: true
  });

  vm.update = function(data) {
    console.log(vm.str);
  };

  vm.check = function() {
    vm.no = 2;
    console.log(vm.str);
  };

  vm.generateQueryParameters = function(url, name, value) {
    var re = new RegExp("([?&]" + name + "=)[^&]+", "");

    function add(sep) {
      url += sep + name + "=" + encodeURIComponent(value);
    }

    function change() {
      url = url.replace(re, "$1" + encodeURIComponent(value));
    }
    if (url.indexOf("?") === -1) {
      add("?");
    } else {
      if (re.test(url)) {
        change();
      } else {
        add("&");
      }
    }
    return url;
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController as ctrl">
  <input type="button" value="Update" ng-click="ctrl.update()">
  <input type="button" value="Check" ng-click="ctrl.check()">
</div>

对于以下对象(来自讨论):

[
  {
    "name": "Node-1",
    "isParent": true,
    "text" : [
       {
           "str" : "/root/get",
       },
       {
          "str" : "/root/get",
       }],
     "nodes": [
      {
        "name": "Node-1-1",
        "isParent": false,
         "text" : [
           {
             "str" : "/root/get",
           },
           {
             "str" : "/root/get",
           }],
           "nodes": [
           {
            "name": "Node-1-1-1",
            "isParent": false,
            "text" : [
            {
              "str" : "/root/get",
            },
            {
              "str" : "/root/get",
            }],
            "nodes": []
          }
        ]
      }
    ]
  }
]

我们可以扩展这种方法 - 参见下面的演示:

var app = angular.module("myApp", []);
app.controller("myController", function() {
  var vm = this;
  vm.no = 1;
  vm._records=[{"name":"Node-1","isParent":true,"text":[{"str":"/root/get",},{"str":"/root/get",}],"nodes":[{"name":"Node-1-1","isParent":false,"text":[{"str":"/root/get",},{"str":"/root/get",}],"nodes":[{"name":"Node-1-1-1","isParent":false,"text":[{"str":"/root/get",},{"str":"/root/get",}],"nodes":[]}]}]}];
  
  vm.setRecords = function(node, url) {
    node.text && node.text.forEach(function(e){
      e.str = url;
    });
    // recursively set url
    node.nodes && node.nodes.forEach(function(e){
      vm.setRecords(e, url);
    });
  }

  Object.defineProperty(vm,
    "records", {
      get: function() {
        let url = vm.generateQueryParameters("/root/get", "no", vm.no);
        vm._records.forEach(function(e){
          vm.setRecords(e, url);
        });
        return vm._records;
      },
      set: function(newValue) {
        // setter function
      },
      enumerable: true,
      configurable: true
    });

  vm.update = function(data) {
    console.log(vm.records);
  };

  vm.check = function() {
    vm.no = 2;
    console.log(vm.records);
  };

  vm.generateQueryParameters = function(url, name, value) {
    var re = new RegExp("([?&]" + name + "=)[^&]+", "");

    function add(sep) {
      url += sep + name + "=" + encodeURIComponent(value);
    }

    function change() {
      url = url.replace(re, "$1" + encodeURIComponent(value));
    }
    if (url.indexOf("?") === -1) {
      add("?");
    } else {
      if (re.test(url)) {
        change();
      } else {
        add("&");
      }
    }
    return url;
  }
});
.as-console-wrapper{top:25px;max-height:calc(100% - 25px)!important;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController as ctrl">
  <input type="button" value="Update" ng-click="ctrl.update()">
  <input type="button" value="Check" ng-click="ctrl.check()">
</div>

【讨论】:

  • 首先非常感谢您为帮助我所做的努力,但是否有必要定义这 2 :可枚举:真,可配置:真?
  • 取决于你是否想enumeratevm的属性...阅读this
  • 现在 str 不会因为这个 getter 和 setter 而成为普通属性,对吧?
  • 它是控制器 objectnormal 属性本身... :)
  • 但是我的 str 变量是对象的一部分,并且该对象绑定到范围变量。所以这会起作用吗?
【解决方案3】:

您需要为 no 添加观察者,它调用您的方法称为 generateQueryParameters 并更改 $scope.str 的值

var app = angular.module("myApp", []);
        app.controller("myController", function ($scope) {
        
        $scope.no = 1;
        $scope.str = "/root/get";
     
        $scope.check = function () {
               console.log($scope.str);
        };
        
        $scope.$watch('no', function() {
          $scope.str = generateQueryParameters($scope.str,
               "no",$scope.no);
        });
           

            $scope.update = function (data) {
              $scope.no=2;
            };
               
    function generateQueryParameters(url,name,value)
    {
        var re = new RegExp("([?&]" + name + "=)[^&]+", "");
        function add(sep) {
            url += sep + name + "=" + encodeURIComponent(value);
        }
        function change() {
            url = url.replace(re, "$1" + encodeURIComponent(value));
        }
        if (url.indexOf("?") === -1) {
            add("?");
        } else {
            if (re.test(url)) {
                change();
            } else {
                add("&");
            }
        }
        return url;
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController">
    <input type="button" value="Update" ng-click="update()">
    <input type="button" value="Check" ng-click="check()">
</div>

先更新再检查

【讨论】:

  • 但是您正在调用 generateQueryParameters。为什么如果不再次调用 generateQueryParameters 方法就不能自动反映该值?
  • 我不想再次调用 generateQueryParameters 方法。因此,当该值更改时,它应该会自动反映在我的 str 变量中而无需任何操作
  • 你需要至少调用一次generateQueryParameters方法在update方法中赋值
  • @Learning :更新后的代码更清晰。现在它只调用一次。
  • 好吧,可以说我的更新方法生成的查询字符串为 no,str 变量中的值为 1。现在我的问题是什么时候我将进行一些操作并且 $scope.no 值会更改为任何内容(我不知道因为我将从服务器接收该值)然后该值会自动在我的 str 中被反映而无需任何操作?
【解决方案4】:

需要在check方法中再次调用generateQueryParameters方法。

$scope.check = function () {
              $scope.no=2;
              $scope.str=generateQueryParameters($scope.str,
               "no",$scope.no);
              console.log($scope.str);
            };

【讨论】:

    【解决方案5】:

    试试这个,问题在于 javascript 原始值是作为值而不是引用传递的。

    var app = angular.module("myApp", []);
        app.controller("myController", function ($scope) {
    
        $scope.testObject = {
           no: 1,
           str: "/root/get"
        };
    
        $scope.update = function (data) {
          $scope.str=generateQueryParameters($scope.testObject,
               "no");
               console.log($scope.testObject);
        };
    
    
            $scope.check = function () {
              $scope.testObject.no=2;
              console.log($scope.testObject.str);
            };
    
    function generateQueryParameters(urlAndValue, name)
    {
        var re = new RegExp("([?&]" + name + "=)[^&]+", "");
        function add(sep) {
            urlAndValue.str += sep + name + "=" + encodeURIComponent(urlAndValue.no);
        }
        function change() {
            urlAndValue.str = urlAndValue.str.replace(re, "$1" + encodeURIComponent(urlAndValue.no));
        }
        if (urlAndValue.str.indexOf("?") === -1) {
            add("?");
        } else {
            if (re.test(urlAndValue.str)) {
                change();
            } else {
                add("&");
            }
        }
        return urlAndValue.str;
    }
    });
    

    【讨论】:

      【解决方案6】:

      试试这个:

      $scope.str = "/root/get";
      $scope.$apply();
      

      【讨论】:

        【解决方案7】:

        你可以使用

        $scope.$apply() 
        

        将更改应用于函数中的范围。它本质上有助于强制您的双向数据绑定开始并赶上您范围内的新变化。如果你正确地实现了数据绑定,你不一定要使用这个命令,但它确实可以作为一个肮脏的快速破解。

        【讨论】:

        • 但这对我有什么帮助?你能解释一下吗
        猜你喜欢
        • 2020-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-21
        • 1970-01-01
        • 2012-03-17
        • 2018-02-18
        相关资源
        最近更新 更多