The key difference between AngularJS and Knockout

    imageRecently, I have managed to participate in discussions several times about why Angular is better or worse than Knockout and other JS frameworks. And very often I came across the fact that there is some misunderstanding of the essence of the differences in the approaches laid down in these products. Sometimes it even came to the point that Knockout cited the default prefixes “data-” as an advantage, which is just ridiculous (not to mention that they can be used in Angular as well).

    I want to fix some thoughts in this article once, which could then be simply referenced. In my opinion, the really key differences between AngularJS and various other frameworks are three in different combinations:

    1. Modular organization of code, testability and brutal warfare with any global data.
    2. Advocating a declarative approach by creating your own HTML directives.
    3. The mechanism for checking data changes in data binding without the use of callbacks.

    And the third point here seems to me the most difficult to understand. Let's talk about him.

    What is data binding? Roughly speaking, this is a display of data in a template, made so that changing the data changes their presentation. Having an object:
    var myViewModel = {
        personName: 'Bob',
        personAge: 123
    };
    
    ... and template:
    <span>personName</span>

    ... we want the span to be myViewModelupdated with the current state with a minimum of our participation. And vice versa, if it is, for example, an input field.

    And to solve this problem, there are two fundamentally different approaches.

    Change listeners

    For data-binding in frameworks such as Knockout and Backbone, a change listeners system has been developed. You work with data not directly, but through a special layer (for example, observables in KO), designed to change the presentation of data in time when they change. Any variable turns into a framework object that monitors the state.

    var myViewModel = {
        personName: ko.observable('Bob'),
        personAge: ko.observable(123)
    };
    ...
    myViewModel.personName('Mary');
    myViewModel.personAge(50);
    

    <spandata-bind="text: personName"></span>


    A smart and obvious solution. It would seem that functional and event-oriented programming in javascript is perfectly suited for such tasks, and indeed, working with callbacks is our strongest point.

    However, there are problems that have to be solved by similar frameworks.

    First, what if one piece of data in some way depends on the other? By changing one variable, we automatically report this, but the second variable that has changed in this case will go unnoticed. To resolve such dependencies in KO, there is a dependency tracking mechanism that works well, but the presence of a solution indicates the existence of a problem.

    Secondly, the change tracking system is triggered mainly at eachchange, because basically it's just callbacks. If we change the data continuously or in large portions at once, then this will cause a lot of unnecessary triggers, because the ultimate goal is just to change the display, which will somehow be brought to the final form, and intermediate states are not needed here.

    Combining the solutions of the previous two points, we get a third problem: continuous micro-desynchronization of the entire data state. Each time, changing something, we cause a corresponding trigger, which in turn can change other data and also cause a trigger. In addition to increasing the execution stack in this way, at each moment of time we run the risk of starting to work with data that is being edited (or prepared for it) somewhere else in the stack, and in order to correctly track such places in the code , you need to understand very well the entire inner kitchen, hidden behind these seemingly simple things; in much the same way as with multi-threaded programming, you need to very carefully monitor the use of shared data.

    Together, such a data-binding mechanism creates such a complex system that other guys decided to cut the Gordian knot and use a fundamentally different approach.

    Dirty checking

    AngularJS works by this principle. Dirty checking is a check for data changes, as simple as two rubles. Previously, the variable myVar was 1, now it is 2 - so the data has changed and you need to redraw them in the template. For simple variables, this is the! = Operator; for complex variables, the corresponding lightweight procedures. This is the simplest approach, which saves us both the need to work with data through a special “listening” layer, and all the problems associated with data dependencies.

    var myViewModel = {
        personName: 'Bob',
        personAge: 123
    };
    ...
    myViewModel.personName = 'Mary';
    myViewModel.personAge = 50;
    

    <span>{{personName}}</span>


    The whole question is when to do this check? Continuously, on a timer? Given that the data model can be quite complex, continuous checks can greatly degrade UX. In Angular, this issue is resolved by automatically calling the $ digest function after each piece of code supposedly able to modify the data . This is the key point - the check is performed if and only if the data could have been changed (for example, under the action of the user), and never performed in other cases. If you expect data to change at some other point in time (for example, when an event arrives from the server or the end of a process), you must explicitly tell Angular that it is worth checking by calling the $ apply function .

    Moreover, the data at each iteration is checked all at once, in its entirety, and not just some part. Before and after the completion of the $ digest execution, we have a stable version of the entire state without desync. If, due to data dependencies, one of their parts has changed during the verification process, then immediately after the end of the current verification, the next one will be scheduled. And the next check again will be performed in its entirety, updating the state of the model completely, eliminating possible problems with unaccounted for dependencies.

    The obvious minus of this approach is performance. Although there is a small exception: for example, when batch updating a large amount of data at once, the check is performed only once at the end, and not every time each of the monitored objects changes, as happens in the first case. But in general, this is nevertheless a minus, since when changing only one variable, a dirty check of all data is performed.

    You just need to understand how strong the performance loss is.

    It is worth noting that Angular never works with the DOM while doing dirty check. All data is native js objects with which all modern browser engines lightly perform most of the basic operations. Although you can embed verification procedures yourself in the dirty check process, Angular documentation strongly recommends that you do not work with the DOM inside them, as this can greatly slow down the whole process.

    Given this, we can say that in practice today there is no loss of performance in web applications. Before, I spent a lot of time developing games for mobile platforms, and there (especially on older ones like Palm OS), every extra processor cycle was usually counted. But even with such a lack of resources, the basic principle of the “data-binding” was the simplest dirty check. What is data-binding in case of a game? This is a display of pictures on the game screen depending on what happens in the state of the game data. Sometimes, in fact, an approach similar to that of listening callbacks was used - for example, updating the screen only in those places where the picture changed. But basically, the screen was simply redrawn each time again entirely, replacing the current frame with the new current state of the graphic. And the only criterion for the validity of this approach was and remains the FPS indicator - how often we can change frames this way and how smooth the corresponding UX will be. As long as the FPS is in the region of the maximum possible for human perception (in the region of 30 frames per second), about performance lossesit just doesn't make sense to think , because they do not lead to anything, which can be called a deterioration in UX.

    The fact is that the simple dirty checking used in AngularJS allows you to work with data of almost any complexity, and at the same time perform checks and change the display in less than 50ms, and this is even longer than if we checked only part of the data but nonetheless instantly for the user. And at the same time, this approach eliminates the many different headaches caused by the complex change listeners mechanism, and simply simplifies the work, because we continue to treat data like ordinary variables and POJO objects, without using the complex syntax of the “listening” layer.

    Also popular now: