Building our task list table with Angular directives

Create an empty tasks.ts file. You might want to use this newly created file to build our new component from scratch and embed on it the definitions of all the accompanying pipes, directives, and components we will see later in this chapter.

Real-life projects are never implemented this way, since our code must conform to the "one class, one file" principle, taking advantage of ECMAScript modules for gluing things together. Chapter 6, Building an Application with Angular Components, will introduce you to a common set of good practices for building Angular applications, including strategies for organizing your directory tree and your different elements (components, directives, pipes, services, and so on) in a sustainable way. This chapter, on the contrary, will leverage tasks.ts to include all the code in a central location and then provide a bird's eye view of all the topics we will cover now without having to go switching across files. Bear in mind that this is in fact an anti-pattern, but for instructional purposes, we will take this approach in this chapter for the last time. The order in which elements are declared within the file is important. Refer to the code repository in GitHub if exceptions rise.

Before moving on with our component, we need to import the dependencies required, formalize the data model we will use to populate the table, and then scaffold some data that will be served by a convenient service class.

Let's begin by adding to our tasks.ts file the following code block, importing all the tokens we will require in this chapter. Pay special attention to the tokens we are importing from the Angular library. We have covered components and input already, but all the rest will be explained later in this chapter:

import { 
Component,
Input,
Pipe,
PipeTransform,
Directive,
OnInit,
HostListener
} from '@angular/core';

With the dependency tokens already imported, let's define the data model for our tasks, next to the block of imports:

/// Model interface
interface Task {
name: string;
deadline: Date;
queued: boolean;
hoursLeft: number;
}

The schema of a Task model interface is pretty self-explanatory. Each task has a name, a deadline, a field informing how many units need to be shipped, and a Boolean field named queued that defines if that task has been tagged to be done in our next session.

You might be surprised that we define a model entity with an interface rather than a class, but this is perfectly fine when the entity model does not feature any business logic requiring implementation of methods or data transformation in a constructor or setter/getter function. When the latter is not required, an interface just suffices since it provides the static typing we require in a simple and more lightweight fashion.

Now, we need some data and a service wrapper class to deliver such data in the form of a collection of Task objects. The TaskService class defined here will do the trick, so append it to your code right after the Task interface:

/// Local Data Service
class TaskService {
public taskStore: Array<Task> = [];
constructor() {
const tasks = [
{
name : 'Code and HTML table',
deadline : 'Jun 23 2015',
hoursLeft : 1
},
{
name : 'Sketch a wireframe for the new homepage',
deadline : 'Jun 24 2016',
hoursLeft : 2
},
{
name : 'Style table with bootstrap styles',
deadline : 'Jun 25 2016',
hoursLeft : 1
}
];

this.taskStore = tasks.map( task => {
return {
name : task.name,
deadline : new Date(task.deadline),
queued : false,
hoursLeft : task.hoursLeft
};
})
}
}

This data store is pretty self-explanatory: it exposes a taskStore property returning an array of objects conforming to the Task interface (hence benefiting from static typing) with information about the name, deadline, and time estimate.

Now that we have a data store and a model class, we can begin building an Angular component that will consume this data source to render the tasks in our template view. Insert the following component implementation after the code you wrote before:

/// Component classes
// - Main Parent Component
@Component({
selector : 'tasks',
styleUrls : ['tasks.css'],
templateUrl : 'tasks.html'
})
export class TaskComponent {
today: Date;
tasks: Task[];
constructor() {
const TasksService: TaskService = new TasksService();
this.tasks = tasksService.taskStore;
this.today = new Date();
}
}

As you can see, we have defined and instantiated through the bootstrap function a new component named TasksComponent with the selector <tasks> (we already included it when we were populating the main index.html file, remember?). This class exposes two properties: today's date and a tasks collection that will be rendered in a table contained in the component's view, as we will see shortly. To do so, it instantiates in its constructor the data source that we created previously, mapping it to the array of models typed as Task objects represented by the tasks field. We also initialize the today property with an instance of the JavaScript built-in Date object, which contains the current date.

As you have seen, the component selector does not match its controller class naming. We will delve deeper into naming conventions at the end of this chapter, as a preparation for Chapter 6, Building an Application with Angular Components.

Let's create the stylesheet file now, whose implementation will be really simple and straightforward. Create a new file named tasks.css at the same location where our component file lives. You can then populate it with the following styles ruleset:

h3, p {
text-align : center;
}

table {
margin: auto;
max-width: 760px;
}

This newly created stylesheet is so simple that it might seem a bit too much to have it as a standalone file. However, this comes as a good opportunity to showcase in our example the functionalities of the styleUrls property of the component metadata.

Things are quite different in regards to our HTML template. This time we will not hardcode our HTML template in the component either, but we will point to an external HTML file to better manage our presentation code. Please create an HTML file and save it as tasks.html in the same location where our main component's controller class exists. Once it is created, fill it in with the following HTML snippet:

<div class="container text-center">
<img src="assets/img/task.png" alt="Task" />
<div class="container">
<h4>Tasks backlog</h4>
<table class="table">
<thead>
<tr>
<th> Task ID</th>
<th>Task name</th>
<th>Deliver by</th>
<th></th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let task of tasks; let i = index">
<th scope="row">{{i}}</th>
<td>{{ task.name | slice:0:35 }}</td>
<span [hidden]="task.name.length < 35">...</span>
</td>
<td>
{{ task.deadline | date:'fullDate' }}
<span *ngIf="task.deadline < today"
class="label label-danger">
Due
</span>
</td>
<td class="text-center">
{{ task.hoursLeft }}
</td>
<td>[Future options...]</td>
</tbody>
</table>
</div>

We are basically creating a table that features a neat styling based on the Bootstrap framework. Then, we render all our tasks using the always convenient ngFor directive, extracting and displaying the index of each item in our collection as we explained while overviewing the ngFor directive earlier in this chapter.

Please look at how we formatted the output of our task's name and deadline interpolations by means of pipes, and how conveniently we display (or not) an ellipsis to indicate when the text exceeds the maximum number of characters we allocated for the name by turning the HTML hidden property into a property bound to an Angular expression. All this presentation logic is topped with a red label, indicating whether the given task is due whenever its end date is prior to this day.

You have probably noticed that those action buttons do not exist in our current implementation. We will fix this in the next section, playing around with state in our components. Back in Chapter 1, Creating Our Very First Component in Angular, we touched upon the click event handler for stopping and resuming the countdown, and then delved deeper into the subject in Chapter 4, Implementing Properties and Events in Our Components, where we covered output properties. Let's continue on our research and see how we can hook up DOM event handlers with our component's public methods, adding a rich layer of interactivity to our components.

..................Content has been hidden....................

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