📜 ⬆️ ⬇️

Angular 1.5: Components


Not so long ago saw the release of the release of Angular 1.5, which introduces many interesting innovations. Important feature

This version is that it is the first of a series of releases that should smooth the conceptual gap between Angular1.x and Angular2.x. For people who have the need to conduct projects on Angular now, but in the future we plan to gradually migrate to Angular2, this is very good news.

In this article I will try to highlight the main innovations:
')

A complete list of changes is available in the Angular repository . We are also waiting for a small example of the use of the listed features.

UI components


Perhaps only the lazy one has not heard about the concept of UI components, so this chapter can be skipped. Or have you not heard? And, heard but do not fully understand? Then I apologize, I will explain the essence of the concept, as well as its advantages.

By component, we will mean a custom element that has some additional behavior and pattern with it. As an example, recall the video or audio elements. Of course, they are not "custom" (according to the W3C specification), but they convey the essence well.

The idea is far from new and it is called hierarchical decomposition. It is used to reduce the complexity oochen for a long time. And why not apply it to the UI of our WEB applications? We take the UI and divide it into separate blocks - components. Each component in turn consists of other components. And those are from others, and so on until we reach the minimum unit — standard elements that can be perceived as components without behavior. If you have worked with Qt, GTK or other GUI frameworks, widgets are components there, so there’s no way to call the idea new.

When choosing the boundaries of each component, we should try to make them as independent as possible from the context of use. In the UI, the component should not be where it is used, as well as what the components it uses consist of.

Interface decomposition


How much should the components be crushed? This is a question of common sense. Naturally, to wrap each DOM element in a component is stupid, because at some point we have to stop. Yet we do it for convenience, and not just. For example, let's take some application with a simple UI. Do you know about TodoMVC ? Let's try to separate it into components:



Immediately I want to note that this option of separation is not the only true one. Therefore, it is worth explaining where these components come from, and why I decided to separate everything that way.

In our application, you can immediately select the elements of the list that describe individual tasks. There is clearly traced behavior and it is clearly the same. Let's call them todo-item Having selected this component, we automatically isolate all the behavior of this part of the UI within the framework of this component. To do separately todo-list does not make sense, since it will be a dummy component, why waste time. We could stuff the list filtering logic into this component, but this is easier to do at the service layer level.

Next we immediately see the todo-header and todo-footer as the todo-header and todo-footer our application. The todo-header component will be responsible for adding new tasks to the list. Of course, we could still slightly split this component and put a separate component inside, isolating the logic of adding tasks, and todo-header would only respond to the layout. Or even more interesting - it will forward the add-through via transclude but ... it is somehow difficult for such a simple application.

todo-footer we also will not split up further, because we will have too small components, and working with them will not be so convenient.

Any component tree must have a root, a base element describing the entire UI. We have this todo-app . It is a kind of entry point describing a specific screen of our application. But our applications are usually more complicated, and have many screenshots, within which you can select additional screenshots, etc. It is for this reason that we have all these routers, etc. But back to this later.

Impact on the development process


Since we can isolate the behavior of each component, as well as having a hierarchy of them, we can very easily develop the development into several developers. Developers will be able to make individual self-contained components, cover them with tests and provide them to other team members.

This greatly affects the flexibility of planning, and the phrase "9 women can not give birth to a child in 1 month" does not so well describe the process of developing our application. By increasing the efficiency of our processes, we can deliver more features to a client in less time, and this is perhaps the most important thing in our work.

There is also another aspect that is not very fond of discussing. This is a segregation of duties between front-end developers and layout makers. It may sound rough, but it is much more effective to take a couple of cheap web designers and one expensive developer frontend, rather than two expensive developers frontends. And given the fact that things like BEM did not appear yesterday, it is not a particular problem to achieve a modular layout. You just need to achieve a certain level of responsibility and understanding from the developers.

We can also take out all the component templates from the JS files, and give the maker-ups the opportunity to work as close as possible to the real place of application of the templates, which reduces the risks associated with the interaction of the team. And the designer does not need to know Angular in order to complete the design. Alternatively, a frontend developer can provide ready-made components to designers with primitive markup and no styles in advance. And the maker-ups will already be engaged in fine-tuning.

So where does Angular 1.5?


In essence, components are directives that define a new element with its own behavior (isolated in the controller) and a template. And as everyone knows, Angular has a wildly re-added directive API. Yes, it is very flexible and allows you to do a lot of things that you usually should not do. And since we need to maintain backward compatibility, it is not possible to simplify it.

That is why for the declaration of components in Angular 1.5, we received a new API:

 class MyComponentController { //     } //       const myComponentDefinition = { //  scope + bindToController: true //         bindings: { 'name': '=' }, //      ,      //   `templateUrl` //       //     template: ` <div class="my-component">{{ $ctrl.name }}</div> `, //           //   `controllerAs`   //           //    `$ctrl`. controller: MyComponentController } //  HTML5       //    .     my-component angular.module('app').component('myComponent', myComponentDefinition); 

A full description of the API, as well as a comparison with the directive API, is available in the documentation . Yet it is useful to glance there sometimes.

So, we can now divide the UI into separate components, it remains only to deal with their state. What is the status of the component? Roughly speaking, this is some kind of data that a component uses to form a view, fill binders, etc. Where does the component get this data from? Requests them from the service, or receives through binding, or on the basis of data from the binding requests from the service. In short, a bunch of options. But which is better?

Stateless vs Stateful components


Facebook folks think that components should be like pure functions . And UI in this case will be only a composition of these functions. That is the formula for happiness from Facebook:

UI = omponents(state)

What does this mean? This means that the state of the data must be obtained from outside the directives and prokkidyvatsya inside through the binding. In this way we make the components more predictable. In the top-level component there will be an entire state for the screen, it will prokidikd the necessary part of the state in the child components and so on.

By making the components independent of the source of the state, we untie our hands on how we will receive and store the state. We can use redux, rx.js, we can use the usual state-mutated approach, we can cache intermediate data, in a word ... we are limited only by our imagination. This was originally the essence of MVC, which was invented back in the 79th year. Complete separation of processing and storage logic from the formation logic of their presentation. Having made this separation, we will not have any problems with the fact that independently change this and that. And as a bonus, testing each individual component or service becomes very simple.

Instead of changing the state directly in the component, it makes sense to ask the services to do this, putting all the responsibility on them. They, in turn, will have to update the state at the top level, which will eventually update the state of the component that launched the entire chain.

Here it is worth mentioning at once that there is a whole mass of tasks when it is not very convenient. And because you can still a little bit of the state to store and change directly in the controller. Usually this state is specific to this particular component. For example - cropping pictures. Agree, it sounds not very reasonable if we drive the data through the services and back to the component for each cursor offset. It is better to store such things locally at the component level and ask the service to do something when we do some more explicit actions.

Components and routing


If we consider the UI as a hierarchy of components, then each screen will be a branch of our tree, and each nested screen will be another branch, etc. And within each new branch we can determine the root, and create a component for it. Deciding which component will be displayed will be decided by our router.

In principle, such approaches have existed since the first versions of Angular, it was just not so convenient to do decomposition. Instead of components, entire states / routes were responsible for isolating individual parts of the UI with their own pattern and behavior embedded in the controllers. Each separate route can be perceived as a full-fledged component, just very fat. Starting from the first versions, the developers have proposed the option of using revolvers to make these “components” easier to use. However, decomposition is still inconvenient, and the sizes of templates and controllers grew rapidly.

The guys from the uiRouter team tried to solve this problem by entering nested views, which can be perceived as splitting the UI into separate components, just not obvious. We can also use resolves to separate the logic of receiving data, and we can also force the update of individual views.

Let's go further! Remove the behavior from the state controller (in essence, remove the controller), replace the template with one component, move the state into it from the resolvers through the binding attributes, and voila - everything is now predictable and easy.

In addition to ngRoute and uiRouter, it is also worth looking at the angular-router, which is an adaptation of the angular2 router for the 1.x branch. In general, we should not forget that soon the world will see the release of uiRouter 1.0 , in which there are also a lot of goodies.

$ scope is not needed!


Let's go even further! $scope not needed! Well, in the context of directives, it is still needed. In particular, that would clean up after itself. But it is not recommended to use it in controllers / services. I really like the idea of ​​adding a rule to eslint that will swear for the presence of $scope somewhere other than the link-functions of the directives. Instead of using $scope we can use the good old javascript and binding on the attributes of the controllers.

This is not something new, the ability to bind values ​​to the controller attributes is not new, this opportunity appeared in angular 1.3, but since most of the examples in the documentation, as well as articles use $scope , I think it would be nice to discuss how can you live without it.

We already considered the question of binding the controller attributes when we discussed the component APIs. We now turn to the rest of the cases, when we really want to use $scope . The first of them, perhaps, will be the use of the $emit/$broadcast/$on event system. Just do not use them. They are not accidentally tied to the hierarchy of scopes, and serve precisely for the notification of individual elements that something has happened. In particular, the use of listeners is usually limited to tracking the $destroy event, on which we must remove everything we left behind and that will not be nailed by the garbage collector. For example, event handlers on document .

Using scop events to organize pub / sub in services, or even worse, tie some application logic to them, this is very bad. And even though in very rare cases this may be useful, I recommend thinking 10 times before using $scope or $rootScope to implement the event system in your application, it is better to use separate libraries designed for this.

Who are we next on the queue? $apply and $digest . These methods allow us to synchronize the state after asynchronous operations. They run the $ digest loop, which collects the changes and starts the handlers. Use these methods only where the asynchronous operation occurs directly. And usually we have all this already wrapped in services. To do something like that in the components is simply unwise. In extreme cases, use the service $timeout . If you are working with DOM events, then again there are directives for this, components should not know anything about DOM.

Well, for sweet - $watch . Oh, how wonderful it is when a developer decides to track state changes in the controllers, and even then to debug it. But what if the top of the data can come to us through the bindings? Suddenly we want to filter the collection, or something else specific. Well ... let's think about it. Values ​​mepsya on the properties of our controller. Unlike the $scope , which is part of the framework, we have all the power over our controller. Continuing to think ... we also have getters / setters! This means that we can accurately determine the moment when our data has changed.

 class MyComponent { get bindedValue() { return this.$value; } set bindedValue(value) { this.$value = value; //    ,    - //  ,      this.makeSomethingWithNewValue(); } } angular.module('app').component('myComponent', { bindings: { "bindedValue": "=" }, template: `<div>- </div>`, controller: MyComponent }); 

This is how simple it is. Actually for this reason, such an interesting concept as Object.observe was excluded drafts of the standard. Well, still think that we really need $scope ?

Unilateral Binding and Isolation


Angular has always been scolded for imposing two-way data binding. Do not misunderstand, double-sided bindings are not bad, especially when working with complex forms. But in most cases it would be possible to do one-sided. It is for this reason that in Angular2 everything is built solely on one-sided binding, and if necessary, two-sided binding, just use a pair of one-sided, acting in opposite directions.

But why is everyone so having bilateral binding, especially in the context of components? As we have said, our components should not be concerned with how components work at lower levels. And we transfer the necessary part of the state from top to bottom, and in principle, what happens to them further does not concern us. But in the case of bilateral binding, we can easily lose control over the system, since the internal components can rewrite the state of the external ones. This is somehow not good. Let's see an example:

 class MyComponent { constructor() { this.myValue = 'take my value'; } } angular.module('app').component('myComponent', { template: ` <my-nasty-component passed-value="$ctrl.myValue"></my-nasty-component> <p>My Value: {{ $ctrl.myValue }}</p> `, controller: MyComponent }); class MyNastyComponent { constructor() { this.passedValue = 'I\'m touching myself tonight!'; } } angular.module('app').component('myNastyComponent', { bindings: { passedValue: '=' }, template: `<span>Mhahaa!</span>`, controller: MyNastyComponent }); 

What do you think, what value will be displayed? Obviously not what we wanted. Yes, of course, the example is contrived, but we can do this by chance and then search for the culprit for a long time. Sometimes it is still worth limiting our capabilities.

So, in Angular 1.5, the long-awaited feature appeared: one-way data binding with the isolation of directives' skoupa ( documentation )! It acts, as the name tells us, at the expense of forwarding the value from the upper level to the lower level, prohibiting changes to walk in the opposite direction. Let's correct the example above, for this we just need to change the bindings of our MyNastyComponent :

 bindings: { passedValue: '<' //    }, 

And that's all, now when the controller of our harmful component changes the value of the passedValue property, it does not go up and remains at this level.

It is important to note that, even though the entire object is not overwritten at the top level, this does not mean that you cannot change the state. It is copying of objects that does not occur, only assignment of values ​​occurs.

 class MyNastyComponent { constructor() { this.passedValue.message = 'I\'m touching myself tonight!'; } } 

In the example above, we change the value of the object field. And since objects in JS are assigned by reference, it changes everywhere. You have to put up with this simply because copying objects can kill performance. Too much price for peace of mind.

So, we learned how to decompose UI into separate reusable elements, and also dealt with one-sided bindings. Just do not forget that we no longer need $scope . Now it would be impossible to poke it alive. Or not?

Multi-slot trasklyud


Our reusable components are not yet fully reusable. There is also a whole class of components that differ in their content, and they can be described as similar "wrappers" for different components. However, these wrappers may also have a behavior. Let's look at an example from material design. Suppose we have a need to make many similar screenshots that differ in content but have a common structure:



? "", - . , , ? ,

(transclude ) . , , , . , ? :



. , . , , transclude: true , ngTransclude . transcludeFn , .

, . angular 1.5 - . . true transclude , :

 transclude: { //       , //     //        slotName: 'elementSelector', //     ,      //         optionalSlotName: '?optionalElementSelector' } 

, ngTransclude :

 <div class="component"> <div class="component-main" ng-transclude="slotName"></div> <div class="component-main" ng-transclude="optionalSlotName">    ,    <em>optionalSlotName</em>   . </div> </div> 

, , . - , . . listView , . , … , . - . : .

?


. — , . .

ngRepeat , ngShow ngIf , . DOM , .

, input , . , require , .

listView , -, pull-to-refresh. . , !


, . , , , , . , ( ). , ( ). , ?

, . Stateless , . , , . .

, Angular , UI . , UI, DOM, , .

— , . , , e2e . Angular2 , . 1.x .

, :

 describe('my component', () => { let component; beforeEach(() => { component = new MyComponent(); component.someState = [1, 3, 2]; }); it('sorts collection before render', () => { expect(component.sortedCollection).toBe.equal([1, 2, 3]); }); }); 

, . Angular . javascript! dirty checking- , DOM, React. UI , .

. , . , angular-mock $componentController , , Angular. ( ) , .

?


, , 4- … , . Angular 1.5 Angular2 . , , ngUpdate. , Angular2 , , Angular2.

?


, - . . , :



, , $scope.$watch - . , , , . . , .

!

Source: https://habr.com/ru/post/277087/


All Articles