-
Book Overview & Buying
-
Table Of Contents
Angular Cookbook
By :
In this recipe, you'll create an attribute directive to calculate the read time of an article, just like Medium. The code for this recipe is highly inspired by my existing repository on GitHub, which you can view at the following link: https://github.com/AhsanAyaz/ngx-read-time.
The project for this recipe resides in chapter02/start_here/ng-read-time-directive:
npm install to install the dependencies of the project. ng serve -o. This should open the app in a new browser tab, and you should see something like this:
Figure 2.2 – ng-read-time-directive app running on http://localhost:4200
Right now, we have a paragraph in our app.component.html file for which we need to calculate the read time in minutes. Let's get started:
read-time. To do that, run the following command: ng g directive directives/read-time
appReadTime directive. We'll first apply this directive to div inside the app.component.html file with the id property set to mainContent, as follows:... <div class="content" role="main" id="mainContent" appReadTime> ... </div>
appReadTime directive. This configuration will contain a wordsPerMinute value, on the basis of which we'll calculate the read time. Let's create an input inside the read-time.directive.ts file with a ReadTimeConfig exported interface for the configuration, as follows:import { Directive, Input } from '@angular/core';
export interface ReadTimeConfig {
wordsPerMinute: number;
}
@Directive({
selector: '[appReadTime]'
})
export class ReadTimeDirective {
@Input() configuration: ReadTimeConfig = {
wordsPerMinute: 200
}
constructor() { }
}ElementRef service to retrieve the textContent property of the element. We'll extract the textContent property and assign it to a local variable named text in the ngOnInit life cycle hook, as follows:import { Directive, Input, ElementRef, OnInit } from '@angular/core';
...
export class ReadTimeDirective implements OnInit {
@Input() configuration: ReadTimeConfig = {
wordsPerMinute: 200
}
constructor(private el: ElementRef) { }
ngOnInit() {
const text = this.el.nativeElement.textContent;
}
}calculateReadTime by passing the text property to it, as follows:...
export class ReadTimeDirective implements OnInit {
...
ngOnInit() {
const text = this.el.nativeElement.textContent;
const time = this.calculateReadTime(text);
}
calculateReadTime(text: string) {
const wordsCount = text.split(/\s+/g).length;
const minutes = wordsCount / this.configuration. wordsPerMinute;
return Math.ceil(minutes);
}
}...
@Directive({
selector: '[appReadTime]'
})
export class ReadTimeDirective implements OnInit {
...
ngOnInit() {
const text = this.el.nativeElement.textContent;
const time = this.calculateReadTime(text);
const timeStr = this.createTimeString(time);
console.log(timeStr);
}
...
createTimeString(timeInMinutes) {
if (timeInMinutes === 1) {
return '1 minute';
} else if (timeInMinutes < 1) {
return '< 1 minute';
} else {
return `${timeInMinutes} minutes`;
}
}
}Note that with the code so far, you should be able to see the minutes on the console when you refresh the application.
@Output() to the directive so that we can get the read time in the parent component and display it on the UI. Let's add it as follows in the read-time.directive.ts file:import { Directive, Input, ElementRef, OnInit, Output, EventEmitter } from '@angular/core';
...
export class ReadTimeDirective implements OnInit {
@Input() configuration: ReadTimeConfig = {
wordsPerMinute: 200
}
@Output() readTimeCalculated = new EventEmitter<string>();
constructor(private el: ElementRef) { }
...
}readTimeCalculated output to emit the value of the timeStr variable from the ngOnInit() method when we've calculated the read time:...
export class ReadTimeDirective {
...
ngOnInit() {
const text = this.el.nativeElement.textContent;
const time = this.calculateReadTime(text);
const timeStr = this.createTimeString(time);
this.readTimeCalculated.emit(timeStr);
}
...
}readTimeCalculated output, we have to listen to this output's event in the app.component.html file and assign it to a property of the AppComponent class so that we can show this on the view. But before that, we'll create a local property in the app.component.ts file to store the output event's value, and we'll also create a method to be called upon when the output event is triggered. The code is shown here:...
export class AppComponent {
readTime: string;
onReadTimeCalculated(readTimeStr: string) {
this.readTime = readTimeStr;
}
}app.component.html file, and we can then call the onReadTimeCalculated method when the readTimeCalculated output event is triggered: ... <div class="content" role="main" id="mainContent" appReadTime (readTimeCalculated)="onReadTimeCalculated($event)"> ... </div>
app.component.html file, as follows:<div class="content" role="main" id="mainContent" appReadTime (readTimeCalculated)="onReadTimeCalculated($event)">
<h4>Read time = {{readTime}}</h4>
<p class="text-content">
Silent sir say desire fat him letter. Whatever settling goodness too and honoured she building answered her. ...
</p>
...
</div>The appReadTime directive is at the heart of this recipe. We use the ElementRef service inside the directive to get the native element that the directive is attached to, then we take out its text content. The only thing that remains then is to perform the calculation. We first split the entire text content into words by using the /\s+/g regular expression (regex), and thus we count the total words in the text content. Then, we divide the word count by the wordsPerMinute value we have in the configuration to calculate how many minutes it would take to read the entire text. Easy peasy, lemon squeezy.