Good day, newbies, today we will try to remake
our toy , learning the basics of new “technologies” for us:
On the Angular we try to rewrite the main parts of our application to find out what it is and what it is eaten with. Therefore, in the first part I will try to describe the course in more detail to you so that you do not break on the pitfalls that are contained in a considerable amount on your way of exploring angular.
Well, in the second part, using DataBoom, we will create a wonderful line of events, as in the original game (I remind you what we are doing in the image of HeartStone). Looking ahead, I’ll say that next time we’ll get rid of the php server altogether, and fully switch to Databoom, but this is a completely different article ...
')
AngularJS
You should start with the fact that to initialize an angular application, it will not be enough for you to simply connect the library and create a module in the js file. To work with angular, you will need to work with your html files as a view, which, in my opinion, is better for separating the view and the controller.
In Angulyar there is such an entity as directives - special html-attributes, and one such we will use to initialize our application. The application may not cover the whole page, but only a separate block on it, it all depends on where you initialize it. Initialization takes place with the help of the ng-app directive:
<html ng-app>
This suggests that this element and everything lower down the tree is the representation of an angular. We will manage all this with the help of controllers, which also need to be initialized in a similar way:
<div ng-controller="myController">
The controllers, in turn, do not hang in the air, but are tied to the modules. The application itself is also a module (the main, entry point to the application), and to set the primacy of one of the modules, its name must be specified in the ng-app directive:
<html ng-app="App">
Modules are created using the angular object's module method:
angular.module(' ', ['_1'])
But the module itself is of little use without controllers. To create a controller on our main module, which we define as an entry point for an application (for example), call the controller method on the module object:
myModule.controller(' ', ['$scope',function($scope){ $scope.var = 'some'; $scope.foo = function(){}; }]);
The $ scope object defines all the variables and functions of the controller's scope, that is, what we can use in the view. In this case, in our html files, we can work with var and foo.
The output of variable values is performed using double curly braces {{var}}, that is:
will print "some".
Closer to the point
We will deal with the rest of the subtleties immediately with examples, since There are some articles on the angular, although the documentation on angular.ru is not quite clear (to me personally).
We will meet the first pitfall when we try to make the application modular (using the example of requirejs). If you immediately register the ng-app directive in html, and then connect the angular library with the require method, we will find that nothing works. This is because the DOM tree at the time of connection of the library has already been compiled.
In this case, the angular object has a bootstrap method:
require(['domReady!'], function (document) { ng.bootstrap(document, ['App']); });
Thus, we bind the App module as an entry point to the application to the document.
The first thing we will remake in our toy is the menu, and the only thing that is there is the list of players.
Code define(['angularControllersModule', 'User', 'Directive'],function(controllers, User, Directive){
Here, the letsFight () method (invitation to battle) will be invoked by clicking on the appropriate button near each player. In Angular, this is given by the ng-click directive:
<li class="{{isMe(user.name)}}" ng-repeat="user in userlist"> <span class="name">{{user.name}}</span> <span class="fightButton" ng-click="letsFight(user.name)"></span> </li>
Pay attention to the different function calls: with curly braces and without. The difference is that the expression in curly brackets is calculated immediately, without waiting for any action from the user, so in the second case we do not use curly brackets, it’s necessary that the function be called only by click.
The ng-repeat directive works in the same way as foreach in php or for of in ES6 - all objects of the list are sorted. The directive is specified in the <li> tag, which means that we will repeat it exactly as many times as we have elements in the userlist array.
But initially our list of players is empty, and we get it via websockets from the server. In the appendix, I tried to separate libraries, modules and some simple instruction sets (I called them Directives) in order to be able to rewrite them without serious consequences.
Moreover, we have a separate handler for these directives (Directives.js), which simply calls the necessary directives by name, which we get from anywhere, for example by websockets or ajax. In the simplest case, this is just apply (), but I created a table in the database that describes how the directives are called. What I mean is: when we try to call a directive with the name myDir, if there is a match in the table, then the directive that is specified there is called, a kind of link. But the point is that the bases are also convenient to set pre and post directives. That is what will be called before and after the call of a specific directive.

And all these directives are stored in a separate folder, connecting when necessary:
modules / directive.js define(['DB','is'],function(DB){ var path = 'Actions/'; var Directive = { run: function(directiveName, args){ var args = args || ['']; if (!is.array(args)) { args = [args] }; DB.getDerictive(directiveName, exec); function exec(directiveName){
By websockets from the server, we get the directive name and arguments, call it using this module:
socket.onmessage = function (e){ if (typeof e.data === "string"){ var request = JSON.parse(e.data); Directive.run(request.function,request.args); }; }
In the same way, we receive instructions on how to add players to our empty list.
Code define(['angularrutch'],function(angularrutch){ var action = { run: function(list){ for(key in list){ angularrutch.scopePush('userListTpl', 'userlist',{name: key}); } } } return action; })
I am sure you have already paid attention to the dependency with the speaker called angular rrutch. This module provides access to the anguly modules from the outside. The fact is that changing the data of the Angulyar controller is not so easy. You can not just call any method or rewrite the value of the parameter. Even if you assign an angular module to some variable, the scope of $ scope will still not be accessible to you directly.
For these purposes, you can use this construction:
var el = document.querySelector( mySelector ); var scope = angular.element(el).scope(); scope.userlist.push(user);
Everything is great, there is access to $ scope, but here it is not so simple. You can change the $ scope data as much as you want, but nothing will change in the view. Angulyar simply did not notice that you changed something, it does not track any change in the $ scope parameters, but does so only in its $ digest () cycle, which is called during certain user actions. To call it manually, call the $ apply method on our scope:
Modified code scope.$apply(function () { scope.userlist.push(user); });
Now everything is in order, the changes that we have made will be visible.
I will not dwell on the controllers of the list of cards in the hands of the players and in the arena, everything is the same here, but we better proceed to the implementation of the event queue.
Queue on DataBoom
All of you (many) know that if you give several tasks in a row and quickly (attack, cast, finish), everything will not be done at the same time, creating a flicker of objects on the screen.
In the database, the queue table looks like this:
{"for":"user_1","motion":"opGetCard","motionId":2}, {"for":"user_2","motion":"opGetCard","motionId":1},
For which player is the action intended, the instruction name and the action id for the order.
For queuing, I created two instructions: the stack.js module with a single push method (the stack is not really a queue, just like the word) and the push method of the DB module responsible for interacting with the DataBoom base:
stack.js define(['DB', 'User'],function(DB, User){ var module = { push: function(forWho, motion, expandObj) { var expandObj = expandObj || null; var motionObj = { 'for': forWho, 'motion': motion }; if (expandObj) { motionObj[expandObj.prop] = [{id:expandObj.id}]; }; DB.push('motionQueue',motionObj); } } return module; })
Using this interface is simple:
stack.push(User.login,'myTimerStart');
Calls the myTimerStart instruction for the User.login user.
The instructions will be retrieved using the simple setInterval () function every 2 seconds:
setInterval(function(){ Directive.run('getNextAction'); }, 2000);
To keep the order, we need a global variable window.motionId, containing the instruction number that has already been worked out (that is, the action has been completed, move on). The getNextAction directive calls the database module method of the same name and describes a callback:
DB.getNextAction(User.login, this.actionStart);
We can search for the necessary instruction in the table due to the possibility of query queries, that is, queries with a filter, sorting and limit:
var config = { baseHost: 'https://t014.databoom.space', baseName: 'b014' } var db = databoom(config.baseHost, config.baseName);
And everything would look simple if we didn’t have complicated instructions like “the player put a card with such parameters on the table”. Here we need to know what the card, what its parameters. Yes, we will create another field in the base “argument” or “map”. Make another request to the database for a selection of information about the map?
DataBoom, thank the gods, has a solution to this effect - the expand option, which says that you need to expand the returned object with data from another table, in our case, a table with maps.
By itself, the binding in the database looks like this:
... ,"card":[{ "id": "probe"}], ...
Record ID in another table. And note that square brackets clearly hint that this is an array, that is, the binding can be multiple.
When expanding the response of the base with the expand instruction, we get the same entry from the base, where instead of the {“id”: “probe”} object will be selected by id from the corresponding table:
{ id:"...", collections:[{ id: "motionQueue"}], "for":"user_1", "motion":"opPutCard", "motionId":2 card:[ { id:"probe", title:"probe", mana:1, attack:1, health:1 } ], }
Conclusion
- All the basic wisdom angular unceremoniously fails to understand, unnecessarily complicated. Difficult to interact from the outside, did not like.
- I liked DataBoom as a whole, although a lot of things are poorly documented in English (although the company, as far as I know, is Russian-speaking), we have to learn some techniques using a poke.
Resources