Change in value of scope variable is not getting reflected in my string

1.8k views Asked by At

I have below string :

"/root/get";

Now I am generating a query string in above string with 1 scope variable but problem is when the value of that variable change then that new value is not getting update in my URL automatically.

You can see in below demo that I have 2 button Update and Check. In update I am generating query string and on check button I am updating the value of scope variable but that is not getting reflected in my URL.

I am not getting why this is happening.

Expected output when I click on check button without calling generateQueryParameters method:

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

Update : I know when the value of $scope.no will change so I don't think I need watcher and I don't want to call generateParameters again.Why angular is not automatically updating the value in my str like the way Angular bindings works?

7

There are 7 answers

13
Niklesh Raut On

You need to add watcher for no which call you method called generateQueryParameters and change the value of $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>

First update and then check

0
Manoj On

you need to call generateQueryParameters method again in check method.

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

Let's start from the issue in your code. You generate the request string through a function, which is actually called only inside the update function

$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);
};

You change the version number in the check function, but without calling again the function in order to change the $scope.str, so the value of $scope.str is still the same.

You can easily test this doing the following steps in your snippet:

  1. Click on update (v0)

  2. Click on check (v0)

  3. But then click again on update and you will see that now it is updated (v2)

So in step 2 you actually change the version, you simply don't generate again the str to be used.

So easy fix for your code is simply to arrange your code in order to call the function for assigning the new str, every time you change your version:

$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() {

}

Otherwise, you need to watch on the no version parameter, and automatically refresh the str value every time the version changes. But this solution implies that you will have a watcher in every single controller where you call the APIs, and also all the parameters always hardcoded inside the controllers:

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

Even if this solution works, actually it sucks. The logic for managing calls is inside the controllers, which is a really bad practice (you should think to use a centralized service for that).

So much better, in AngularJS you can use a custom interceptor and manage there all the actions to be performed regarding HTTP requests. So you can specify as a parameter of the HTTP request, the version of the API you want to use.

This will keep your code clean. Moreover, if you want to change in the future some request, you can simply change that request parameter. If you want to change all the requests, inside the interceptor you can simply set that all the requests to version 1 will be replaced by version 2.

Here a sample code on how to define the interceptor:

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
  }

}

Then simply perform as always the HTTP requests through $http adding the version you want to use inside the request as a parameter:

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

So the interceptor will "sniff" all your requests before to be performed, it will correctly compose the version and the query string as you want to, and all your operations are managed within it and centralized.

Just as the last tip, when defining constants like version numbers, the endpoint of the rest service, whatever, use AngularJS constant and inject them where you need to use them. Hard-coded strings are not a good practice.

17
kukkuz On

You can easily define a property str using the new controller as syntax (allows you to directly bind to controller properties and methods) as such:

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

This means everytime you access the value of str, it re-evaluates the url string. This makes your code independent of $scope and $watch and is more forward-looking.

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>

For the below object (from discussion):

[
  {
    "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": []
          }
        ]
      }
    ]
  }
]

we can extend this approach - see demo below:

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>

0
3960278 On

try this, problem is with javascript primitive values are passed as values not references.

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;
}
});
0
Manoj Meria On

Try this:

$scope.str = "/root/get";
$scope.$apply();
1
Rohan Kanjani On

You can use

$scope.$apply() 

to apply a change to your scope in a function. It essentially helps force your two-way data-binding to kick off and catch on to new changes in your scope. If you implement data-binding correctly, you don't have to necessarily use this command, but it does work as a dirty quick hack.