How does data binding work in AngularJS?

前端 未结 14 1511
情话喂你
情话喂你 2020-11-21 05:41

How does data binding work in the AngularJS framework?

I haven\'t found technical details on their site. It\'s more or less clear how it works when data

相关标签:
14条回答
  • 2020-11-21 06:08

    This is my basic understanding. It may well be wrong!

    1. Items are watched by passing a function (returning the thing to be watched) to the $watch method.
    2. Changes to watched items must be made within a block of code wrapped by the $apply method.
    3. At the end of the $apply the $digest method is invoked which goes through each of the watches and checks to see if they changed since last time the $digest ran.
    4. If any changes are found then the digest is invoked again until all changes stabilize.

    In normal development, data-binding syntax in the HTML tells the AngularJS compiler to create the watches for you and controller methods are run inside $apply already. So to the application developer it is all transparent.

    0 讨论(0)
  • 2020-11-21 06:11

    Misko already gave an excellent description of how the data bindings work, but I would like to add my view on the performance issue with the data binding.

    As Misko stated, around 2000 bindings are where you start to see problems, but you shouldn't have more than 2000 pieces of information on a page anyway. This may be true, but not every data-binding is visible to the user. Once you start building any sort of widget or data grid with two-way binding you can easily hit 2000 bindings, without having a bad UX.

    Consider, for example, a combo box where you can type text to filter the available options. This sort of control could have ~150 items and still be highly usable. If it has some extra feature (for example a specific class on the currently selected option) you start to get 3-5 bindings per option. Put three of these widgets on a page (e.g. one to select a country, the other to select a city in the said country, and the third to select a hotel) and you are somewhere between 1000 and 2000 bindings already.

    Or consider a data-grid in a corporate web application. 50 rows per page is not unreasonable, each of which could have 10-20 columns. If you build this with ng-repeats, and/or have information in some cells which uses some bindings, you could be approaching 2000 bindings with this grid alone.

    I find this to be a huge problem when working with AngularJS, and the only solution I've been able to find so far is to construct widgets without using two-way binding, instead of using ngOnce, deregistering watchers and similar tricks, or construct directives which build the DOM with jQuery and DOM manipulation. I feel this defeats the purpose of using Angular in the first place.

    I would love to hear suggestions on other ways to handle this, but then maybe I should write my own question. I wanted to put this in a comment, but it turned out to be way too long for that...

    TL;DR
    The data binding can cause performance issues on complex pages.

    0 讨论(0)
  • 2020-11-21 06:11

    Here is an example of data binding with AngularJS, using an input field. I will explain later

    HTML Code

    <div ng-app="myApp" ng-controller="myCtrl" class="formInput">
         <input type="text" ng-model="watchInput" Placeholder="type something"/>
         <p>{{watchInput}}</p> 
    </div>
    

    AngularJS Code

    myApp = angular.module ("myApp", []);
    myApp.controller("myCtrl", ["$scope", function($scope){
      //Your Controller code goes here
    }]);
    

    As you can see in the example above, AngularJS uses ng-model to listen and watch what happens on HTML elements, especially on input fields. When something happens, do something. In our case, ng-model is bind to our view, using the mustache notation {{}}. Whatever is typed inside the input field is displayed on the screen instantly. And that's the beauty of data binding, using AngularJS in its simplest form.

    Hope this helps.

    See a working example here on Codepen

    0 讨论(0)
  • 2020-11-21 06:12

    Angular.js creates a watcher for every model we create in view. Whenever a model is changed, an "ng-dirty" class is appeneded to the model, so the watcher will observe all models which have the class "ng-dirty" & update their values in the controller & vice versa.

    0 讨论(0)
  • 2020-11-21 06:13

    By dirty checking the $scope object

    Angular maintains a simple array of watchers in the $scope objects. If you inspect any $scope you will find that it contains an array called $$watchers.

    Each watcher is an object that contains among other things

    1. An expression which the watcher is monitoring. This might just be an attribute name, or something more complicated.
    2. A last known value of the expression. This can be checked against the current computed value of the expression. If the values differ the watcher will trigger the function and mark the $scope as dirty.
    3. A function which will be executed if the watcher is dirty.

    How watchers are defined

    There are many different ways of defining a watcher in AngularJS.

    • You can explicitly $watch an attribute on $scope.

        $scope.$watch('person.username', validateUnique);
      
    • You can place a {{}} interpolation in your template (a watcher will be created for you on the current $scope).

        <p>username: {{person.username}}</p>
      
    • You can ask a directive such as ng-model to define the watcher for you.

        <input ng-model="person.username" />
      

    The $digest cycle checks all watchers against their last value

    When we interact with AngularJS through the normal channels (ng-model, ng-repeat, etc) a digest cycle will be triggered by the directive.

    A digest cycle is a depth-first traversal of $scope and all its children. For each $scope object, we iterate over its $$watchers array and evaluate all the expressions. If the new expression value is different from the last known value, the watcher's function is called. This function might recompile part of the DOM, recompute a value on $scope, trigger an AJAX request, anything you need it to do.

    Every scope is traversed and every watch expression evaluated and checked against the last value.

    If a watcher is triggered, the $scope is dirty

    If a watcher is triggered, the app knows something has changed, and the $scope is marked as dirty.

    Watcher functions can change other attributes on $scope or on a parent $scope. If one $watcher function has been triggered, we can't guarantee that our other $scopes are still clean, and so we execute the entire digest cycle again.

    This is because AngularJS has two-way binding, so data can be passed back up the $scope tree. We may change a value on a higher $scope that has already been digested. Perhaps we change a value on the $rootScope.

    If the $digest is dirty, we execute the entire $digest cycle again

    We continually loop through the $digest cycle until either the digest cycle comes up clean (all $watch expressions have the same value as they had in the previous cycle), or we reach the digest limit. By default, this limit is set at 10.

    If we reach the digest limit AngularJS will raise an error in the console:

    10 $digest() iterations reached. Aborting!
    

    The digest is hard on the machine but easy on the developer

    As you can see, every time something changes in an AngularJS app, AngularJS will check every single watcher in the $scope hierarchy to see how to respond. For a developer this is a massive productivity boon, as you now need to write almost no wiring code, AngularJS will just notice if a value has changed, and make the rest of the app consistent with the change.

    From the perspective of the machine though this is wildly inefficient and will slow our app down if we create too many watchers. Misko has quoted a figure of about 4000 watchers before your app will feel slow on older browsers.

    This limit is easy to reach if you ng-repeat over a large JSON array for example. You can mitigate against this using features like one-time binding to compile a template without creating watchers.

    How to avoid creating too many watchers

    Each time your user interacts with your app, every single watcher in your app will be evaluated at least once. A big part of optimising an AngularJS app is reducing the number of watchers in your $scope tree. One easy way to do this is with one time binding.

    If you have data which will rarely change, you can bind it only once using the :: syntax, like so:

    <p>{{::person.username}}</p>
    

    or

    <p ng-bind="::person.username"></p>
    

    The binding will only be triggered when the containing template is rendered and the data loaded into $scope.

    This is especially important when you have an ng-repeat with many items.

    <div ng-repeat="person in people track by username">
      {{::person.username}}
    </div>
    
    0 讨论(0)
  • 2020-11-21 06:15

    I wondered this myself for a while. Without setters how does AngularJS notice changes to the $scope object? Does it poll them?

    What it actually does is this: Any "normal" place you modify the model was already called from the guts of AngularJS, so it automatically calls $apply for you after your code runs. Say your controller has a method that's hooked up to ng-click on some element. Because AngularJS wires the calling of that method together for you, it has a chance to do an $apply in the appropriate place. Likewise, for expressions that appear right in the views, those are executed by AngularJS so it does the $apply.

    When the documentation talks about having to call $apply manually for code outside of AngularJS, it's talking about code which, when run, doesn't stem from AngularJS itself in the call stack.

    0 讨论(0)
提交回复
热议问题