The right level of encapsulation

Our task list is displayed correctly and the code we used to achieve this looks quite okay. However, if we want to follow a better approach for composition, we should rethink the design of our task-list component. If we draw a line at enlisting the task list responsibilities, we would come up with things such as listing tasks, adding new tasks to the list, and sorting and filtering the task list; however, operations are not performed on an individual task itself. Also, rendering the task itself falls outside of the responsibilities of the task list. The task-list component should only serve as a container for tasks.

If we look at our code again, we will see that we're violating the single responsibility principle and rendering the whole task body within our task-list component. Let's take a look at how we can fix this by increasing the granularity of the encapsulation.

The goal now is to do a code refactoring exercise, also known as extraction. We are pulling our task's relevant template out of the task list template and creating a new component that encapsulates the tasks.

For this, we need to create a new sub folder within the task-list folder called task. Within this folder, we will create a template file with the name task.html:

<input type="checkbox" [checked]="task.done">
<div class="task__title">{{task.title}}</div>

The content of our new task.html file is pretty much the same as what we already have within our task-list.html template. The only difference is that we will now refer to a new model called task.

Now, within the task folder, let's create the JavaScript file, task.js, which will contain the controller class of our component:

import {Component, Input, ViewEncapsulation} from '@angular/core';
import template from './task.html!text';

@Component({
  selector: 'ngc-task',
  host: {
    class: 'task'
  },
  template,
  encapsulation: ViewEncapsulation.None
})
export class Task {
  // Our task model can be attached on the host within the view
  @Input() task;
}

In the previous chapter of this book, we spoke about encapsulation and the preconditions to establish a clean encapsulation for UI components. One of these preconditions is the possibility to design proper interfaces in and out of the component. Such input and output methods are necessary to make the component work within compositions. That's how a component will receive and publish information.

As you can see from our task component implementation, we are now building such an interface using the @Input annotation on a class instance field. In order to use this annotation, we will first need to import it from the angular core module.

Input properties in Angular allow us to bind the expressions in our templates to class instance fields on our components. This way, we can pass data from the outside of the component to the component inside, using the components template. This can be thought of as an example of one-way binding, from the view to the component.

If we're using property binding on a regular DOM property, Angular will create a binding of the expression directly to the element's DOM property. We're using such a type of binding to bind the task completed flag to the checked property of the checkbox's input element:

Usage

Description

@Input() inputProp;

This allows us to bind the inputProp attribute to the component element within the parent component.

Angular assumes that the attribute of the element has the same name as that of the input property.

@Input('inp') inputProp;

You can also override the name of the attribute that should be mapped to this input. Here, the inp attribute of the component's HTML element is mapped to the component's input property, inputProp.

The last missing piece to use our newly created task component is the modification of the existing template of the task list.

We include the task component within our task list template by using an <ngc-task> element, as specified in the selector within our task component. Also, we create a property binding on the task element. There, we pass the task object from the current NgFor iteration to the task input of the task component. We need to replace all the existing content in the task.html file with the following lines of code:

<ngc-task *ngFor="let task of tasks" 
          [task]="task"></ngc-task>

In order to make our task-list component recognize the task component element, we need to add it to the task-list component's directives property within the task-list.js file:

...
import {Task} from './task/task';

@Component({
  ...
  directives: [Task]
})
...

Congratulations! You've successfully refactored your task list by extracting the task into its own component and have established a clean encapsulation. Also, we can now say that our task list is a composition of tasks.

If you think about maintainability and reusability, this was actually a very important step in the process of building our application. You should constantly look out for such encapsulation opportunities, and if you feel something could be arranged into multiple subcomponents, you should probably go for it. Of course, you can also overdo this. There's simply no golden rule to determine what granularity of encapsulation is the right one.

Tip

The right granularity of encapsulation for a component architecture always depends on the context. My personal tip here is to use known principles from OOP, such as single responsibility, to lay the groundwork for a good design of your component tree. Always make sure your components are only doing things that they are supposed to do as their names suggest. A task list has the responsibility of listing tasks and providing some filters or other controls for the list. The responsibility of operating on individual task data and rendering the necessary view clearly belongs to a task component and not the task list.

Recap

In this building block, we cleaned up our component tree and established clean encapsulation using subcomponents. Then, we set up the interfaces provided by Angular using input bindings. We performed these actions by following the ensuing steps:

  1. We created a task subcomponent.
  2. We used the task subcomponent with the task-list component.
  3. We used input bindings and DOM element property bindings to establish one-way data binding in the task component.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset