The tasks feature

The tasks feature encompasses some more logic since it entails two components and a directive. Let's begin by creating the core unit required by TaskTooltipDirective:

import { Task } from './task.model';
import { Input, Directive, HostListener } from '@angular/core';

@Directive({
selector: '[task]'
})
export class TaskTooltipDirective {
private defaultTooltipText: string;
@Input() task: Task;
@Input() taskTooltip: any;

@HostListener('mouseover')
onMouseOver() {
if (!this.defaultTooltipText && this.taskTooltip) {
this.defaultTooltipText = this.taskTooltip.innerText;
}
this.taskTooltip.innerText = this.defaultTooltipText;
}
}

The directive keeps all the original functionality in place and just imports the Angular core types and task-typing it requires. Let's look at the TaskIconsComponent now:

import { Component, Input, OnInit } from '@angular/core';
import { Task } from './task.model';

@Component({
selector: 'task-icons',
template: `
<img *ngFor="let icon of icons"
src="/app/shared/assets/img/pomodoro.png"
width="{{size}}">`
})
export class TaskIconsComponent implements OnInit {
@Input() task: Task;
@Input() size: number;
icons: Object[] = [];

ngOnInit() {
this.icons.length = this.task.noRequired;
this.icons.fill({ name : this.task.name });
}
}

So far so good. Now, let's jump to TasksComponent. This will consist of:

  • The component file tasks.component.ts, where the logic is described in TypeScript
  • The CSS file tasks.component.css, where the styles are defined
  • The template file tasks.component.html, where the markup is defined

Starting with the CSS file, it will look like this:

// app/task/tasks.component.css

h3, p {
text-align: center;
}

.table {
margin: auto;
max-width: 860px;
}

Continuing on with the HTML markup:

// app/task/tasks.component.html

<div class="container text-center">
<h3>
One point = 25 min, {{ queued | i18nPlural: queueHeaderMapping }}
for today
<span class="small" *ngIf="queued > 0">
(Estimated time : {{ queued * timerMinutes | formattedTime }})
</span>
</h3>
<p>
<span *ngFor="let queuedTask of tasks | queuedOnly: true">
<task-icons
[task]="queuedTask"
[taskTooltip]="tooltip"
size="50">
</task-icons>
</span>
</p>
<p #tooltip [hidden]="queued === 0">
Mouseover for details
</p>
<h4>Tasks backlog</h4>
<table class="table">
<thead>
<tr>
<th>Task ID</th>
<th>Task name</th>
<th>Deliver by</th>
<th>Points required</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let task of tasks; let i = index">
<th scope="row">{{ (i+1) }}
<span *ngIf="task.queued" class="label label-info">
Queued</span>
</th>
<td>{{ task.name | slice:0:35 }}
<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.noRequired }}</td>
<td>
<button
type="button"
class="btn btn-default btn-xs"
[ngSwitch]="task.queued"
(click)="toggleTask(task)">
<ng-template [ngSwitchCase]="false">
<i class="glyphicon glyphicon-plus-sign"></i>
Add
</ng-template>
<ng-template [ngSwitchCase]="true">
<i class="glyphicon glyphicon-minus-sign"></i>
Remove
</ng-template>
<ng-template ngSwitchDefault>
<i class="glyphicon glyphicon-plus-sign"></i>
Add
</ng-template>
</button>
</td>
</tr>
</tbody>
</table>
</div>

Please take a moment to check out the naming convention applied to the external component files, whose filename matches the component's own to identify which file belongs to what in flat structures inside a context folder. Also, please note how we removed the main bitmap from the template and replaced the hardcoded time durations with a variable named timerMinutes in the binding expression that computes the time estimation to accomplish all queued tasks. We will see how that variable is populated in the following component class:

// app/task/tasks.component.ts

import { Component, OnInit } from '@angular/core';
import { TaskService } from './task.service';
import { Task } from "./task.model";
import { SettingsService } from "../core/settings.service";

@Component({
selector: 'tasks',
styleUrls: ['tasks.component.css'],
templateUrl: 'tasks.component.html'
})
export class TasksComponent implements OnInit {
today: Date;
tasks: Task[];
queued: number;
queueHeaderMapping: any;
timerMinutes: number;

constructor(
private taskService: TaskService,
private settingsService: SettingsService) {
this.tasks = this.taskService.taskStore;
this.today = new Date();
this.queueHeaderMapping = this.settingsService.pluralsMap.tasks;
this.timerMinutes = this.settingsService.timerMinutes;
}

ngOnInit(): void {
this.updateQueued();
}

toggleTask(task: Task): void {
task.queued = !task.queued;
this.updateQueued();
}

private updateQueued(): void {
this.queued = this.tasks
.filter((Task: Task) => Task.queued)
.reduce((no: number, queuedTask: Task) => {
return no + queuedTask.noRequired;
}, 0);
}
}

Several aspects of the TasksComponent implementation are worth highlighting. Firstly, we can inject the TaskService and SettingsService in the component, leveraging Angular's DI system. The dependencies are injected with accessors right from the constructor, becoming private class members on the spot. The tasks dataset and the time duration are then populated from the bound services.

Let's now add all these constructs to the TaskModule, that is, the file task.module.ts and export everything that is either a directive or a component. It is worth noting, however, that we do this because we think all these constructs may need to be referred to somewhere else in the app. I urge you to strongly consider what to put in the exports keyword and what not to put there. Your default stance should be to put as little as possible for exporting:

import { NgModule } from '@angular/core';
@NgModule({
declarations: [TasksComponent, TaskIconsComponent, TasksTooltipDirective],
exports: [TasksComponent],
providers: [TaskService]
// the rest omitted for brevity
})

We have now added the constructs to the declarations keyword so that the module is aware of them and also the exports keyword so that other modules importing our TaskModule are able to use them. The next task is to set up our AppComponent, or root component, as it is also known as.

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

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