📜 ⬆️ ⬇️

Ember.js - goodbye MVC (part 1)

I bring to your attention the translation of the article Ember.js - Goodbye MVC (Part 1) .

At the EmberConf 2015 conference, Yehuda Katz and Tom Dale reported that some changes in Ember 2 would soon appear. In particular, routed components attracted the most attention. They allow you to recognize controllers obsolete and remove them. Of course, this alarmed many Ember users, since Ember and Sproutcore have always been MVC client frameworks.

Goodbye MVC


It is no secret that many new agreements and changes in Ember 2 arise under the direct influence of React and Flux (the pattern for the data flow in React applications). What is called “unidirectional data flow” in Flux, in Ember is the “data down, action up” pattern (DDAU).

Instead of the traditional client MVC pattern, DDAU offers a single data stream (hence the focus in one direction), which makes it easier to perceive application code and improve performance. The fundamental problem with MVC is revealed as the application grows and becomes more complex. Cascade update and unobvious dependencies lead to confusion and confusion. Updating one object leads to a change in another, which, in turn, launches the following processes and ultimately turns support for the application into a real “nightmare”.
')
image Ember 2.0 Data Down, Actions UP

In the DDAU pattern, data flows are unidirectional, and there are no two-way bindings. Different parts of the application may remain largely incoherent and comprehensible. This means that you will always know the source of the change object. If you read my post on functional programming and the observer effect , then you will understand why it is so important to preserve the independence of components from third-party effects.

Instead of spending time building a self-made framework with a dozen micro-libraries and meaningless searches for the best way to implement a function, you should use Ember, which offers performance and a low entry threshold for the developer right away. In combination with the DDAU pattern and the Glimmer visualization engine, the Ember developers initially have both performance and efficiency at their disposal.

Preparing the application for DDAU


When the routed components appear, the controllers will expire and be removed from the framework. Controllers and views have always confused new Ember users, and “in 80% of the cases of application they played the role of a component” (in this video from Yehuda and Tom you can learn more).

Since the components are not singletones, when the Glimmer sees fit, they will be disassembled and displayed again in an optimized form. But if the controllers are removed, how to cope with saving the state?

For example, you have some property in the controller. It holds the state you would like to keep in the application. To do this in Ember 2, we can remove this property of the controller and put in its place “components with support for services”. The service will save the state of a single component and directly enter it only when necessary. Since services provide many features, some developers may abuse them. I will tell about services at the end of the article.

Implementation of components with support services


In the following example, I will demonstrate how you can use services and unidirectional bindings today. You can read the text below and use this demo in parallel.

This small application consists of several checkboxes with a selection of animals. This sample should be saved on different routes and restore state when returning to the route. We need to define a simple service that saves the state for the selected elements and then injects it into the component being routed.

In the route pattern, we can simply display the entered service status via the helper each .

{{! animals/index.hbs }} <div class="row"> <div class="col-md-3"> <h2>Select Animals</h2> {{checkbox-group group=animals selectedItems=checkboxGroup.selectedItems check=(action "check") }} </div> <div class="col-md-9"> <h3>Selected Animals</h3> <table class="table table-bordered"> <thead> <tr> <th>ID</th> <th>Species</th> <th>Name</th> </tr> </thead> <tbody> {{#each checkboxGroup.selectedItems as |animal|}} <tr> <td>{{animal.id}}</td> <td>{{animal.species}}</td> <td>{{animal.name}}</td> </tr> {{/each}} </tbody> </table> </div> </div> 

In our controller or routed component, we enter the service and determine the action to process the marked animal. The set of states of the service is then transferred to the component, keeping it as clean as possible. Although you could just enter the service into the component, the above method will allow you to clarify the process and separate the component from the service.

 // animals/controller.js import Ember from 'ember'; const { inject: { service }, Controller } = Ember; const OPERATION_MAP = { true: 'addObject', false: 'removeObject' }; export default Controller.extend({ checkboxGroup: service(), // In the future, actions will be defined in the route and passed into the // routable component as `attributes`. actions: { check(group, item, isChecked) { return group[OPERATION_MAP[isChecked]](item); } } }); 

As mentioned above, the service itself is simple. Later we can define more complex behavior, but here the underlying persistence for the state is just an array of javascript.

 // checkbox-group/service.js import Ember from 'ember'; const { Service } = Ember; export default Service.extend({ init() { this._super(...arguments); this.selectedItems = []; } }); 

Since in this example we are using simple behavior, there is no need to define a subclass of the component. The check action is passed from the routed component / controller, so using closed actions in the component template means that we should not cast sendActions to void.

In our component template, we use small composable helpers. These helpers are, in essence, simple JavaScript functions. And since they return values, we can use them as subexpressions of Handlebars, where we could once determine the computational property.

The helper contains is not included in Ember initially, but the function itself is a single line of code . There are a number of useful additions that add helpers to the application. For example, I myself use the ember-truth-helpers supplement in almost every application.

 {{! checkbox-group/template.hbs }} {{#each group as |item|}} <div class="checkbox"> <label for={{concat "item-" item.id}}> {{one-way-input id=(concat "item-" item.id) class="checkbox" type="checkbox" checked=(contains selectedItems item) update=(action this.attrs.check selectedItems item) }} {{item.name}} <span class="label label-default">{{item.species}}</span> </label> </div> {{/each}} 

As mentioned in my previous article , the addition of ember-one-way-input is the easy way to use one-way bindings right now.

I hope that this simple example clearly shows how you can create a maintainable and efficient application with the help of some of my favorite Ember features: helpers, closed actions, components, and one-way bindings.

Caution about services


Here, everything is as in the well-known expression: "The greater the strength, the greater the responsibility." Since the services in Ember are singletons, you will be tempted to want to do several services and enter them everywhere.

If you create a service only to use it as a global object, then in general the code will be “stuffy” because the dependencies will not be obvious (we have already figured out that this is bad), and parts of the application will become closely related. Instead, expose the data and actions through the interfaces to keep the code separate and clear. Remember the principle of strength and responsibility and use the service only when absolutely necessary!

When to use services?


I like the answer from Stack Overflow regarding the use of singletons. In fact, you should use them only when you can have only one copy in the entire application. For example, a shopping cart in an online store, activity tape or instant messages could be excellent candidates for using the service.



Article Translation - Ember.js - Goodbye MVC (Part 1)

Special thanks to the translators and participants of the emjs.ru website for providing the article.

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


All Articles