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

7

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?

Dashboard answered 5/9, 2017 at 12:39 Comment(1)
Did you try a watcher for the scope variable?Banal
S
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.

Signora answered 5/9, 2017 at 13:28 Comment(1)
Upvoted for your kind efforts towards helping me and for such a brief answer :)Dashboard
P
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>
Perspective answered 21/9, 2017 at 2:42 Comment(17)
First of all thank you so much for your kind efforts towards helping me but is it necessary to define this 2 : enumerable: true, configurable: true?Dashboard
depends if you want to enumerate properties of vm... Read thisPerspective
Now str will not be normal property because of this getter and setter right?Dashboard
its a normal property itself of the controller object... :)Perspective
But my str variable is a part of object and that object is binded to scope variable.So this will work?Dashboard
see, str is now a property of the controller object... we don't use $scope when using the controller as syntax... the snippet above works right?Perspective
Let us continue this discussion in chat.Dashboard
Sorry but i havent figure out a solution.So as a quick fix i have to call generateQueryParameters method again to reflect new value i.e $scope.no = 2Dashboard
@Learning see updated answer in which I have extended the answer for the records object... let me know if this works for you, thanks!Perspective
Is that variable binded to scope variable?Dashboard
In a way, binded to controller object using the controller as syntax...Perspective
But as you are using getter and setter than i dont think now its a normal property.Dashboard
records is similar to str in the first example, and it's a normal property, only re-computing the url... :)Perspective
Ok so you are saying that when $scope.no=1 becomes $scope.no=2 then again generateQueryParameters function will called and it will regenerate my url.Is that right?Dashboard
@Learning when you access records variable, the get function will be internally invoked and yes, the url will be regenerated using generateQueryParameters function...Perspective
@Learning so the updated answer fits your use-case? Let me know, thanks!Perspective
Let us continue this discussion in chat.Dashboard
F
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

Felspar answered 5/9, 2017 at 12:45 Comment(13)
But you are calling generateQueryParameters.Why cant that value is not automcatically reflecting without calling generateQueryParameters method again?Dashboard
I dont want to call generateQueryParameters method again.So when that value is changed that should automatically reflect in my str variable without any operationDashboard
You need to call generateQueryParameters method at least once to assign inside update methodFelspar
@Learning : updated code is more clear. Now its called only one time.Felspar
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?Dashboard
No, for that you need to add watcherFelspar
Let us continue this discussion in chat.Dashboard
@Learning : I updated my code with watch with variable no . Now you are not calling generateQueryParameters method from update method its automatically called by watcherFelspar
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?Dashboard
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.Felspar
So there is no way to automate this??Dashboard
In angular its watcherFelspar
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 variableDashboard
I
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);
            };
Idiom answered 5/9, 2017 at 12:50 Comment(0)
I
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.

Infant answered 22/9, 2017 at 19:26 Comment(1)
But how does this apply helps me?Can you please explain me a little bitDashboard
A
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;
}
});
Armoire answered 23/9, 2017 at 14:46 Comment(0)
H
0

Try this:

$scope.str = "/root/get";
$scope.$apply();
Hurty answered 24/9, 2017 at 7:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.