6

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?

halfer
  • 19,824
  • 17
  • 99
  • 186
I Love Stackoverflow
  • 6,738
  • 20
  • 97
  • 216

7 Answers7

3

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
quirimmo
  • 9,800
  • 3
  • 30
  • 45
1

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>
kukkuz
  • 41,512
  • 6
  • 59
  • 95
0

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

Niklesh Raut
  • 34,013
  • 16
  • 75
  • 109
  • But you are calling generateQueryParameters.Why cant that value is not automcatically reflecting without calling generateQueryParameters method again? – I Love Stackoverflow Sep 05 '17 at 12:51
  • I dont want to call generateQueryParameters method again.So when that value is changed that should automatically reflect in my str variable without any operation – I Love Stackoverflow Sep 05 '17 at 12:53
  • You need to call `generateQueryParameters` method at least once to assign inside `update` method – Niklesh Raut Sep 05 '17 at 12:53
  • @Learning : updated code is more clear. Now its called only one time. – Niklesh Raut Sep 05 '17 at 12:55
  • Ok lets say my update method generated querystring for no with value 1 inside str variable right.Now my question is when i will have some operation and $scope.no value gets changed to anything(i dont know because i will receive that value from server) then will that value automcatically gets relected in my str without any operation? – I Love Stackoverflow Sep 05 '17 at 12:56
  • No, for that you need to add `watcher` – Niklesh Raut Sep 05 '17 at 12:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/153690/discussion-between-learning-and-user2486). – I Love Stackoverflow Sep 05 '17 at 12:58
  • @Learning : I updated my code with watch with variable `no` . Now you are not calling `generateQueryParameters` method from `update` method its automatically called by watcher – Niklesh Raut Sep 05 '17 at 13:00
  • But i know when the value of $scope.no will change so i dont think i need watcher and i dont want to call generateParameters again.Why angular is not automatically updating the value in my str like the way angular bindings works? – I Love Stackoverflow Sep 05 '17 at 13:04
  • In any condition you need to call this method, whenever any changed happened. You just change the way but ultimately you need to come to this method. – Niklesh Raut Sep 05 '17 at 13:08
  • So there is no way to automate this?? – I Love Stackoverflow Sep 05 '17 at 13:10
  • In angular its watcher – Niklesh Raut Sep 05 '17 at 13:10
  • No i mean when value of scope.no change i dont need to call again generateQueryParameters and that change value gets reflected automatically in str variable – I Love Stackoverflow Sep 05 '17 at 13:12
0

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);
            };
Manoj
  • 1,175
  • 7
  • 11
0

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.

0

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;
}
});
3960278
  • 756
  • 4
  • 12
0

Try this:

$scope.str = "/root/get";
$scope.$apply();
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
Manoj Meria
  • 586
  • 6
  • 13