In this blog post I will talk about the changes coming in Angular 2 that will improve its support for functional programming.
Angular 2 is still in active development, so all the examples are WIP. Please focus on the capabilities and ideas, and not the API. The API may change.
WHY FUNCTIONAL PROGRAMMING
Imagine an application being a nested structure of components. And it is written in the way that
- A component depends only on its bindings and its direct children.
- A component directly affects only its direct children.
For example, the highlighted Todo component should not be able to affect any other component except for the input elements.
If we write our application in this way, we get the property:
We can effectively reason about every single component without knowing where it is used. In other words, we can reason about a component just by looking at its class/function and its template.
We want to limit the number of things we need to look at to make a change. And we want to make changes predictable and controlled. This is the goal, and functional programming is just the means of achieving it.
DATA-ORIENTED PROGRAMMING
Using a mutable domain model goes against our goal. Say we have a mutable domain model that backs up my component. And the component updates the model. Some other component, somewhere else, that happened to point it, will be updated.
Instead we should model our domain using dumb data structures, such as record-like objects and arrays, and transform them using map, filter, and reduce.
Since Angular does not use KVO, and uses dirty-checking instead, it does not require us to wrap our data into model classes. So we could always use arrays and simple objects as our model.
class TodosComponent { constructor(http:Http) { this.todos = []; http.get("/todos"). then((resp) => { this.todos = resp.map((todo) => replaceProperty(todo, "date", humanizeDate)) }); } checkAll() { this.todos = this.todos.map((todo) => replaceProperty(todo, 'checked', true)); } numberOfChecked() { return this.todos.filter((todo) => todo.checked).length; } }
Here, for example, todos is an array of simple objects. Note, we do not mutate any of the objects. And we can do it because the framework does not force us to build a mutable domain model.
We can already write this kind of code in Angular today. What we want is to make it smoother, and take advantage of it performance wise.
USING PERSISTENT DATA STRUCTURES
For example, we want to improve the support of persistent data structures.
First, we want to support even most exotic collections. And, of course, we do not want to hardcode this support into the framework. Instead, we are making the new change detection system pluggable. So we will be able to pass any collection to ng-repeat or other directives, and it will work. The directives will not have to have special logic to handle them.
// what is todos? array? immutable list? does not matter.
Second, we can also take advantage of the fact that these collections are immutable. For example, we can replace an expensive structural check to see if anything changed with a simple reference check, which is much faster. And it will be completely transparent to the developer.
CONTROLLING CHANGE DETECTION
Functional programming limits the number of ways things change, and it makes the changes more explicit. So because our application written in a functional way, we will have stronger guarantees, even though the JavaScript language itself does not provide those guarantees.
For example, we may know that a component will not change until we receive some event. Now, we will be able to use this knowledge and disable the dirty-checking of that component and its children altogether.
A few examples where this feature can come handy: * If a component depends on only observable objects, we can disable the dirty-checking of the component until one of the observables fires an event. * If we use the FLUX pattern, we can disable the dirty-checking of the component until the store fires an event.
class TodosComponent { constructor(bingings:BindingPropagationConfig, store:TodoStore) { this.todos = ImmutableList.empty(); store.onChange(() => { this.todos = store.todos(); bingings.shouldBePropagated(); }); } }
FORMS & NG-MODEL
Our applications are not just projections of immutable data onto the DOM. We need to handle the user input. And the primary way of doing it today is NgModel.
NgModel is a convenient way of dealing with the input, but it has a few drawbacks:
- It does not work with immutable objects.
- It does not allow us to work with the form as a whole.
For instance, if in the example below todo is immutable, which we would prefer, NgModel just will not work.
So NgModel forces us to copy the attributes over and reassemble them back after we are done editing them.
class TodoComponent { todo:Todo; description:string; checked:boolean; actions:Actions; constructor(actions:Actions){ this.actions = actions; } set todo(t) { this.todo = t; this.description = t.description; this.checked = t.checked; } updateTodo() { this.actions.updateTodo(this.todo.id, {description: this.description, checked: this.checked}); } }
This is not a bad solution, but we can do better than that.
A better way of dealing with forms is treating them as streams of values. We can listen to them, map them to another stream, and apply FRP techniques.
Template:
Component:
class FiltersComponent { constructor(fb:FormBuilder, actions:Actions) { this.filtersForm = fb({ description: string, checked: boolean }); this.filtersForm.values.debounce(500).forEach((formValue) => { actions.filterTodos(formValue); }); } }
We can also take the current value of the whole form, like shown below.
Template:
Component:
class TodoComponent { todo:Todo; actions:Actions; constructor(actions:Actions) { this.actions = actions; } updateTodo(formValue) { this.actions.updateTodo(this.todo.id, formValue); } }
SUMMARY
- We want to be able to reason about every component in isolation. And we want changes to be predictable and controlled. Functional programming is a common way of achieving it.
- Data-oriented programming is a big part of it. Although Angular has always supported it, there are ways to make it even better. For example, by improving support of persistent data structures.
- When using functional programming we have stronger guarantees about how things change. We will be able to use this knowledge to control change detection.
- The current way of dealing with dealing with the user input, although convenient, promotes mutability. A more elegant way is to treat forms as values, and streams of values.
-
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
- •
- •
-
-