Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 440
Learning-Overthinker-Confused Avatar asked Sep 05 '17 12:09

Learning-Overthinker-Confused


People also ask

What happens when a variable value type data goes out of its scope?

Nothing physical happens. A typical implementation will allocate enough space in the program stack to store all variables at the deepest level of block nesting in the current function. This space is typically allocated in the stack in one shot at the function startup and released back at the function exit.

Is VAR function scoped?

The main difference between keywords var and let is that variables declared using let are block-scoped, while var is function scoped.

How does var scope work with variables?

JavaScript has function scope: Each function creates a new scope. Variables defined inside a function are not accessible (visible) from outside the function. Variables declared with var , let and const are quite similar when declared inside a function.

How do you determine the scope of a variable in JavaScript?

The static structure of a program determines the variable scope. The scope of a variable is defined by its location within the source code, and nested functions have access to variables declared in their outer scope.


2 Answers

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.

like image 77
quirimmo Avatar answered Oct 04 '22 16:10

quirimmo


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>
like image 25
kukkuz Avatar answered Oct 04 '22 18:10

kukkuz