Good day dear, habracheloveki.
In this article, I want to share with you my experience with such frameworks as
AngularJS and
Knockout .
The article will be interesting to those who are familiar with JavaScript and have an idea of ​​at least one of the mentioned frameworks and naturally want to expand their horizons.
Overview
AngularJS and Knockout are very close in their ideology. They are frameworks for dynamic web applications and use HTML as a template. They allow you to extend the HTML syntax in order to describe the components of your application more clearly and concisely. Out of the box, they eliminate the need to write code that was previously created to implement the model-view-controller connection. AngularJS and Knockout are essentially what HTML and JavaScript would be if they were designed to create modern web applications. HTML is a great declarative language for static documents. But, unfortunately, there is not much that is needed to create modern web applications.
Features
- Data-binding: a simple and good way to communicate UI and data models.
- A powerful set of tools for the developer (in particular, AngularJS, Knockout has a rather poor set)
- Easy expandable toolkit
')
Application architecture
According to the documentation, Angular offers to structure the application, dividing it into modules. Each module consists of:
- the function that configures the module — it starts immediately after the module is loaded;
- controller;
- services;
- directives.
The controller in the understanding of Angular is a function that constructs a data model. The service $ scope is used to create the model, but a little further about it. Directives are extensions for HTML.
In turn, Knockout offers to build an application, dividing it into ModelView, which is a mix of model and controller. Within the ko.bindingHandlers object, data-bindings are placed that are analogous to Angular directives. To build a connection between a model and its representation, observable and observableArray are used.
Speaking of modularity, one can not forget about the AMD pattern - Asynchronous Module Definition. Angular and Knockout do not have their own implementation of the AMD template. I advise you to use the
RequireJS library. It has proven itself very well in terms of compatibility with both Angular and Knockout. More information about her can be found here:
http://www.kendoui.com/blogs/teamblog/posts/13-05-08/requirejs-fundamentalsals.aspx and
http://habrahabr.ru/post/152833/ .
Templates
(Special thanks to the developers of AngularJS for such a beautiful picture)
At the moment there are already a huge number of template engines. For example, jQuery Templates (unfortunately, no longer supported). Most of them work on the principle: take a static template as a string, mix it with data, create a new string, and insert the resulting string into the necessary DOM element via innerHTML properties. Such an approach means rendering the template each time after any data change. In this approach, there are a number of known problems, for example: reading user input and connecting it to a model, losing user data due to overwriting it, managing the entire data update process and / or presentation. In addition, this approach, in my opinion has a negative impact on performance.
Angular and Knockout take a different approach. Namely two-way binding. A distinctive feature of this approach is the creation of a bidirectional link between a page element and model elements. This approach allows you to get a fairly stable DOM. In Knockout, bidirectional communication is implemented through the observable and observableArray functions. The jQuery HTML parser is used to analyze the template (if connected, otherwise a similar native parser). The result of the work of these functions is a function that encapsulates the current state of the model element and is responsible for two-way binding. This implementation, in my opinion, is not very convenient because there is a problem associated with copying the state of the model: the function’s skoup is not copied, so you must first get the data from the model element by referring to it as a function and only after that clone the result.
In Angular, a bi-directional link is built directly by the compiler (the $ compile service). The developer does not need to use functions like observable. In my opinion, this is much more convenient since there is no need to use additional structures and there is no problem when copying the state of the model element.
The key difference in the implementation of templating in Angular and Knockout is the way the elements are rendered: Angular generates DOM elements that it then uses; Knockout - generates strings and innerHTML them. Therefore, the generation of a large number of elements takes more time from Knockout (a vivid example is slightly lower).
Data model
Speaking about the data model in Angular, you should definitely stop at the $ scope service. This service contains links to the data model. Since Angular assumes a rather complex application architecture, $ scope also has a more complex structure.
Inside each module, a new instance of $ scope is created, which is a successor of $ rootScope. It is possible to programmatically create a new instance of $ scope from the existing one. In this case, the created instance will be the heir of the $ scope from which it was created. Dealing with the $ scope hierarchy in Angular is easy for those who know JavaScript well. This feature is very convenient when there is a need to create different widgets, for example pop-ups.
Data-binding
Binding in Knockout, directive in Angular is used to extend the HTML syntax, that is, to teach the browser new tricks. I will not analyze the concept of data-bindings and directives in detail. I just want to note that data-binding is the only way to display data in Knockout and its connection with the view.
In more detail this issue is considered in the articles:
AngularJS:
http://habrahabr.ru/post/164493/ ,
http://habrahabr.ru/post/179755/ ,
http://habrahabr.ru/post/180365/
KnockoutJS:
http://www.knockmeout.net/2011/07/another-look-at-custom-bindings-for.html
Separately, I want to mention the presence of filters from Angular. Filters are used to format the data displayed on the screen. Unfortunately, Knockout uses bindings for everything.
Examples
Fade-in animation
AngularJS: http://jsfiddle.net/yVEqU/var ocUtils = angular.module("ocUtils", []); ocUtils.directive('ocFadeIn', [function () { return { restrict: 'A', link: function(scope, element, attrs) { $(element).fadeIn("slow"); } }; }]); function MyCtrl($scope) { this.$scope = $scope; $scope.items = []; $scope.add = function () { $scope.items.push('new one'); } $scope.pop = function () { $scope.items.pop(); } }
Knockout: http://jsfiddle.net/fH3TY/ var MyViewModel = { items: ko.observableArray([]), fadeIn: function (element) { console.log(element); $(element[1]).fadeIn(); }, add: function () { this.items.push("fade me in aoutomatically"); }, pop: function () { this.items.pop(); } }; ko.applyBindings(MyViewModel, $("#knockout")['0']);
I think that it will be difficult to find something simpler than this example, it perfectly demonstrates the syntax of the frameworks.
Fade-out animation
AngularJS: http://jsfiddle.net/SGvej/ var FADE_OUT_TIMEOUT = 500; var ocUtils = angular.module("ocUtils", []); ocUtils.directive('ocFadeOut', [function () { return { restrict: 'A', link: function(scope, element, attrs) { scope.$watch(attrs["ocFadeOut"], function (value) { if (value) { $(element).fadeOut(FADE_OUT_TIMEOUT); } }); } }; }]); function MyCtrl($scope, $timeout) { this.$scope = $scope; $scope.items = []; $scope.add = function () { $scope.items.push({removed: false}); } $scope.pop = function () { $scope.items[$scope.items.length - 1].removed = true; $timeout(function () { $scope.items.pop(); console.log($scope.items.length); }, FADE_OUT_TIMEOUT); } }
Knockout: http://jsfiddle.net/Bzb7f/1/ var MyViewModel = { items: ko.observableArray([]), fadeOut: function (element) { console.log(element); if (element.nodeType === 3) { return; } $(element).fadeOut(function () { $(this).remove(); }); }, add: function () { this.items.push("fade me in aoutomatically"); }, pop: function () { this.items.pop(); } }; ko.applyBindings(MyViewModel, $("#knockout")['0']);
This example is not much more complicated than the previous one, but there are several nuances.
In the case of Angular, the fadeOut must be executed before removing the element, since the DOM-element is associated with this element of the model and will be deleted the moment the element is deleted. It is also important to note that the removal of a model element from an array should be done through the $ timeout service. This service is essentially a wrapper for the setTimeout function and ensures the integrity of the data model.
Knockout has a different problem. The fadeOut function takes as its first argument an array of DOM elements related to this model element. Sometimes under strange circumstances, in the process of rendering a template can be created and, accordingly, they will be present in the resulting array, so you need to check the elements before performing fadeOut. Also, at the end of the fadeOut process, do not forget to delete DOM elements (they are not automatically deleted).
Popup
AngularJS: http://jsfiddle.net/vmuha/EvvY7/ ,
http://angular-ui.imtqy.com/bootstrap/ (on the second link you will find quite a lot of good and useful solutions)
var ocUtils = angular.module("ocUtils", []); function MyCtrl($scope, $compile) { var me = this; this.$scope = $scope; $scope.open = function (data) { var popupScope = $scope.$new(); popupScope.data = data; me.popup = $("<div class=\"popup\">{{data}}<br /><a href=\"#\" ng-click=\"close($event)\"> Close me</a></div>"); $compile(me.popup)(popupScope); $("body").append(me.popup); } $scope.close = function () { if (me.popup) { me.popup.fadeOut(function () { $(this).remove(); }); } } }
Knockout: http://jsfiddle.net/vmuha/uwezZ/ ,
http://jsfiddle.net/vmuha/HbVPp/ var jQueryWidget = function(element, valueAccessor, name, constructor) { var options = ko.utils.unwrapObservable(valueAccessor()); var $element = $(element); setTimeout(function() { constructor($element, options) }, 0); //$element.data(name, $widget); }; ko.bindingHandlers.dialog = { init: function(element, valueAccessor, allBindingsAccessor, viewModel) { console.log("init"); jQueryWidget(element, valueAccessor, 'dialog', function($element, options) { console.log("Creating dialog on " + $element); return $element.dialog(options); }); } }; ko.bindingHandlers.dialogcmd = { init: function(element, valueAccessor, allBindingsAccessor, viewModel) { $(element).button().click(function() { var options = ko.utils.unwrapObservable(valueAccessor()); $('#' + options.id).dialog(options.cmd || 'open'); }); } }; var viewModel = { label: ko.observable('dialog test') }; ko.applyBindings(viewModel);
Implement popup can be different. Through directive or binding and as part of a ViewModel or module.
In Angular for popup, you will need to create a new instance of $ scope, as I mentioned above, and use the $ compile service to compile the template.
In Knockout, you also most likely need to create a new ModelView and call the applyBindings function to link the model and the view. I think it's worth noting that if a new data model is created for the popup, then Knockout will have trouble getting access to $ rootModel from the popup template. The data model hierarchy in Knockout is built on DOM elements, respectively, if the popup container is outside the application container, then the popup will not have access to $ rootModel.
Price formatting
AngularJS: http://jsfiddle.net/vmuha/k6ztB/1/
Knockout: http://jsfiddle.net/vmuha/6yqDw/
Performance
We turn to the issue of performance. Two tests were made: cold start of the “Hello World!” Application and rendering of an array of 1000 elements.
On all circuits vertically - milliseconds, horizontally the number of the experiment.
Here you can clearly see that the Knockout cold start is much faster than the Angular.
But when it comes to rendering, Angular is obviously leading the way. As we can see, for rendering 1000 rows, Knockout spends up to 2.5 seconds while Angular lasts less than 500 milliseconds to complete this task. In addition, the display of the rendered items on the user's screen also takes different times: for Angular, this is 1-3 seconds, and for Knockout - 14-20 seconds. This happens because Knockout generates strings, and Angular generates DOM elements.
Summary
The main question for me was to determine the scope of Angular and Knockout. After a few simple experiments, I made the following conclusions:
Knockout is applicable in cases when there is no need to create a complex architecture, complex workflows. Its main function is to relate the model and the presentation, so it is best used for simple one-page applications. For example, the creation of various levels of complexity of forms.
Regarding Angular, I came to the conclusion that it will be useful in cases where the creation of RichUI is required. A real and complete one-page application with a complex architecture and complex connections.
PS:
I hope this article will be interesting to all. I will be glad to read your comments, feedback and constructive criticism! I wish you all a pleasant job!