One time binding: update model and re-render view

11,347

Solution 1

I was trying to figure out some way to do this elegantly as well. I wish there was something built into the framework to refresh one-time bindings. All I came up with is using ngIf to remove the element I wanted to refresh and the add it back.

Here's a demo. Click the Add Item button, you'll see that the list does not refresh due to the one-time binding on the repeat. Check the refresh values and click again, and the items will be updated:

var app = angular.module('demo', []);

app.controller('RefreshCtrl', function($scope, $timeout) {
  var counter = 4;

  $scope.visible = true;

  $scope.items = ['Item1', 'Item2', 'Item3'];

  $scope.addItem = function() {

    if ($scope.refresh) {
      $scope.visible = false;
    }

    $scope.items.push('Item' + counter);

    counter++;

    $timeout(function() {
      $scope.visible = true;
    });
  };

});
<script src="https://code.angularjs.org/1.3.17/angular.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

<div ng-app="demo" ng-controller="RefreshCtrl" class="container">
  <button class="btn btn-default" ng-click="addItem()">Add Item</button>
  <input type="checkbox" ng-model="refresh" />Refresh Values
  <div ng-if="visible">
    <h3 ng-repeat="item in ::items">{{item}}</h3>
  </div>

  <p>Items Array: {{items}}</p>
</div>

Solution 2

Depending on what you are after, I would recommend one of two solutions:

I'm the author of the former, and the big difference between it and other solutions is the choice of hooking into the $parse service.

As such, you can use the introduced {{:refreshkey:expression}}/:refreshkey:expression syntax in most (if not all) areas of Angular where an expression is accepted.


In your case, the implementation could look something like this:

js

angular.module('app', []).controller('AppCtrl', function($scope) {
  $scope.items = [
      {id: 1},
      {id: 2},
      {id: 3}
  ];

  $scope.addAndRefresh = function() {
      $scope.items.push({id: 4});
      /**
       * '$$rebind' is the internal namespace used by angular-bind-notifier.
       * 'refresh' is the refresh key used in your view.
       */
      $scope.$broadcast('$$rebind:refresh');
  };
});

markup

<!-- HTML template -->
<div ng-repeat="item in :refresh:items">
    {{::item.id}}
</div>
<button ng-click="addAndRefresh()">Add</button>

Or, if you wanted something semi-dynamic

js

angular.module('app', []).controller('AppCtrl', function($scope) {
  $scope.items = [
      {id: 1},
      {id: 2},
      {id: 3}
  ];

  $scope.add = function() {
      $scope.items.push({id: 4});
  };
});

markup

<!-- HTML template -->
<div bind-notifier="{ refresh: items.length }">
  <div ng-repeat="item in :refresh:items">
      {{::item.id}}
  </div>
</div>
<button ng-click="add()">Add</button>

Check out the README and this jsBin for some usage examples.

Share:
11,347

Related videos on Youtube

Matteo Piazza
Author by

Matteo Piazza

Updated on September 15, 2022

Comments

  • Matteo Piazza
    Matteo Piazza over 1 year

    I was wondering if possible, using angular one time binding, to completely re-render the view/template after a model update, also by recompiling the template. For instance, on a button press, maybe in the way react works: I update the model and explicitly force to update the view. Basically here is what I am trying to achieve:

    // controller
    angular.module('app', []).controller('AppCtrl', function($scope) {
        $scope.items = [
            {id: 1},
            {id: 2},
            {id: 3}
        ];
    
        $scope.addAndRefresh = function() {
            $scope.items.push({id: 4});
            // manually call render logic here???
        };
    });
    
    
    <!-- HTML template -->
    <div ng-repeat="item in ::items">
        {{item.id}}
    </div>
    <button ng-click="addAndRefresh()">Add</button>
    

    By clicking on the "Add" button I would like to refresh the view to see the newly added item.

    • Matteo Piazza
      Matteo Piazza almost 9 years
      Bizzarre: I always thought stackoverflow is the right place for such a question. I have a clear problem (it seems to me) I do not know how to approach. Maybe someone wiser than me could help!
    • kll
      kll almost 9 years
      @MatteoPiazza Check my answer for a couple of solutions to your problem. Not built into the angular core, but it's probably as close as you will get to that until Angular 2.0.
  • Matteo Piazza
    Matteo Piazza almost 9 years
    Thx, that's a way I was also exploring, but, like you, I was also looking for an elegant way.
  • Devon Sams
    Devon Sams over 7 years
    Beware of kcd-recompile. I recently discovered that child component scopes are not destroyed upon recompile. Over the course of a long session and several recompiles, this will bring things to a crawl. github.com/kentcdodds/kcd-angular/issues/8
  • Mathias Conradt
    Mathias Conradt over 6 years
    Thanks for this solution. Interestingly it only works when you use the $timeout function of Angular. Using the native JS setTimeout() function (which I tried at first) does not work - then the visible change does not get recognized by the view.
  • Nurbol Alpysbayev
    Nurbol Alpysbayev about 6 years
    @MathiasConradt it is because setTimeout does not start new digest cycle. The cycle loops through all the watches (including ng-if) and refreshes/recompiles everything.