Oxenarf
Oxenarf

Reputation: 278

How to bind AngularJs variable inside javascript data field?

I would like to bind a variable took from a json file inside a javascript data field, but i can't use the {{}} operator because it's processed after.

<div class="milestone">
  <div class="number" data-animation="true" data-animation-type="number" data-final-number="{{itemSold}}"></div>
  <div class="title">Items Sold</div>
</div>

In this way it gives to me a NaN because he can't see the value of itemSold.

This is how itemSold is retrived

var app = angular.module('app', []);
app.controller('AppCtrl', ['$scope', '$http', function($scope, $http) 
{
  $http.get('app/shared/homeStatistics.json').success(function(data){
   $scope.itemSold = data.itemSold;
   $scope.themeAndTemplate = data.themeAndTemplate;
   $scope.members = data.members;
  });
}]);

I think that i have to use something like ng-bing that is processed before, but i dont know how.

Thanks for all suggestions and sorry for my bad english

EDIT 1

The data are correctly retrieved but are processed after the data-final-number so he read a "" at the beginning

my json data

{
"itemSold":1000
}

EDIT 2

here it how it is processed the data-final-number

var handlePageScrollContentAnimation = function() {
$('[data-scrollview="true"]').each(function() {
    var myElement = $(this);

    var elementWatcher = scrollMonitor.create( myElement, 60 );

    elementWatcher.enterViewport(function() {
        $(myElement).find('[data-animation=true]').each(function() {
            var targetAnimation = $(this).attr('data-animation-type');
            var targetElement = $(this);
            if (!$(targetElement).hasClass('contentAnimated')) {
                if (targetAnimation == 'number') {
                    var finalNumber = parseInt($(targetElement).attr('data-final-number'));
                    $({animateNumber: 0}).animate({animateNumber: finalNumber}, {
                        duration: 1000,
                        easing:'swing',
                        step: function() {
                            var displayNumber = handleAddCommasToNumber(Math.ceil(this.animateNumber));
                            $(targetElement).text(displayNumber).addClass('contentAnimated');
                        }
                    });
                } else {
                    $(this).addClass(targetAnimation + ' contentAnimated');
                }
            }
        });
    });
});
};

Upvotes: 2

Views: 1919

Answers (5)

Pankaj Parkar
Pankaj Parkar

Reputation: 136124

I'd like to put watch on 'itemSold' variable and whenever value fetched from $http call, i'll call handlePageScrollContentAnimation handler.

var watchItemSold = $scope.$watch('itemSold', function(newVal, oldVal, scope){
   if(newVal != oldVal){
      handlePageScrollContentAnimation(); //call handler
      watchItemSold(); //watch destructor will ensure that handler will call once
   }   
});

Hope this will help you. Thnaks.

Upvotes: 0

Khanh TO
Khanh TO

Reputation: 48972

I suggest using attr.$observer with a finalNumber directive. This will trigger a function to be executed whenever there is an update. With that said, it's not only rendered one time, whenever the value changes, the view is updated.

.directive('finalNumber',function() {

  function link(scope, element, attrs) {
    $attrs.$observe('finalNumber', function(value) {
       if (!isNaN(value)){
          //update accordingly, it's kind of hack to
          //bring this code to angular. It's better to write all these
          // as angular directives.
          handlePageScrollContentAnimation(); 
       }
    });
  }

  return {
    link: link
  };
});

You need to change your mindset when moving to angular from jQuery background. Angular is an MVC framework and based mainly on databinding. With data binding, the view should not care when and how the model is updated, but whenever there is an update, the view should be aware of it and update the view accordingly.

The above example should be the right way to work with angular. But as I said, bring your jQuery code to angular is quite a hack, all these should be written as directives. I'm not sure whether you need to run your jQuery code only once (running multiple times may cause side effects) after the first update. You may need a bit of hack. This is not recommended and if possible, you should write all these as directives (scrollview,animation,finalNumber,..)

.directive('finalNumber',function() {

      function link(scope, element, attrs) {
        var hasRun = false;//hack the code to run only once.
        $attrs.$observe('finalNumber', function(value) {
           if (!isNaN(value) && !hasRun){
              //update accordingly, it's kind of hack to
              //bring this code to angular. It's better to write all these
              // as angular directives.
              handlePageScrollContentAnimation(); 
           }
        });
      }

      return {
        link: link
      };
    });

Upvotes: 1

falsarella
falsarella

Reputation: 12437

Apparently, you're trying to handle the data-final-number="{{itemSold}}" attribute before it has been resolved. To handle it after, make sure to only call the handler after the you have retrieved the JSON data and the AngularJS lifecycle have resolved it already for you. To do that, you can use AngularJS's $timeout, so it will be enqueued and executed after AngularJS manipulation.

var app = angular.module('app', []);
app.controller('AppCtrl', ['$scope', '$http', function($scope, $http, $timeout) 
{
  $http.get('app/shared/homeStatistics.json').success(function(data){
   $scope.itemSold = data.itemSold;
   $scope.themeAndTemplate = data.themeAndTemplate;
   $scope.members = data.members;

   //calling handler after AngularJS applied all two-way-bindings
   $timeout(function() {
     handlePageScrollContentAnimation();
   }, 0);

  });
}]);

For further information, read: Anyway to trigger a method when Angular is done adding scope updates to the DOM?

Upvotes: 0

Serhii Halchenko
Serhii Halchenko

Reputation: 722

If I understood you correctly, you want to have the data just after the component/directive/controller is loaded.

In that case - use resolve in controller, it will inject the result of your ajax request into controller and by the time controller is loaded you'll have all the data.

Router

app.config(function ($routeProvider) {
  $routeProvider
    .when('/',
    {
      templateUrl: "app.html",
      controller: "AppCtrl"
      resolve: {
        statResponse: function () {
          return $http.get('app/shared/homeStatistics.json');
        }
      }
    }
  )
});

Controller

app.controller('AppCtrl', ['$scope', 'statResponse', function($scope, statResponse) 
{
  if(statResponse && statResponse.status === 200) {
   $scope.itemSold = statResponse.data.itemSold;
   $scope.themeAndTemplate = statResponse.data.themeAndTemplate;
   $scope.members = statResponse.data.members;
  }
}]);

Another way is to use ng-cloak on the element. It will hide the element until all variables are resolved.

<div class="number" ng-cloak data-animation="true" data-animation-type="number" data-final-number="{{itemSold}}"></div>

Hope that helps.

Upvotes: 0

Unrealsolver
Unrealsolver

Reputation: 142

It's OK in most cases. If you don't want any NaNs, you have few options:

  • Initialize scope variables with, e.g., 'Pending...'
  • Wrap it with ng-if or ng-show
  • More complicated solutions (e.g. rest-angular)

Upvotes: 0

Related Questions