Exercise: TypeScript Fundermentals

Background

Imagine you're working at Facebook and your job is to implement the "Like" functionality.

When a user clicks the "Like" button below a post, the button is highlighted (to indicate that it is selected) and the number of likes is increased.

You're going to implement this feature in Angular and for that you'll need to create a component. This component is a TypeScript class that encapsulates the data for rendering the like button (eg: the number of likes, and whether the button is in the on/off state). It also responds to user actions. So, when the user clicks the "Like" button, the number of likes should be increased and the button should be in the "selected/on" state. If the user clicks the button again, the number of likes should be decreased and the button should be in the "unselected" state.

Spec

For the purpose of this exercise, forget about the HTML. Your focus should be purely on defining a TypeScript class with the right members (fields, properties, methods, constructor).

Allow the consumer of this class to pass the initial number of likes when creating an instance of this class.

Define this class in a separate module and use it in the main module. Simulate the scenario where the user clicks the like component. Display the total number of likes and whether the button is in the selected or unselected state on the console.

Solution

  • Create a file called "like.component.ts"
In [1]:
// File: like.component.ts
export class LikeComponent {
   constructor(public likesCount, public isSelected){
  }

  onClick(){
    if (this.isSelected){
      this.likesCount--;
      this.isSelected = false;
    } else {
      this.likesCount++;
      this.isSelected = true;
    }
  }
}
Out[1]:
[Function: LikeComponent]
In [10]:
// Refactor with less code
// File: like.component.ts
export class LikeComponent {
   constructor(public likesCount, public isSelected){

  }

  onClick(){
    this.likesCount += (this.isSelected)? -1: 1;
    this.isSelected = !this.isSelected;
  }
}
Out[10]:
[Function: LikeComponent]
  • Remember to import module in "main.ts"
In [ ]:
// File: main.ts
import {LikeComponent} from './like.components';

let component = new LikeComponent(10, true);
component.onClick();
console.log(`likesCount: ${component.likesCount}, isSelected: ${component.isSelected}`);

Compile & Run

  • Mac/Linux
    • tsc *.ts && node main.js
  • Windows
    • tsc *.ts
    • node main.js

A Problem with the Current Implementation

  • Before calling the "onClick" method, changes can be made to fields "likesCount" and "isSelected"
  • The fields can only be changed if we click the Like button
  • To prevent this, we should implement a read-only property on these fields so that changes of these fields can only happen when we click a button.
  • We can accomplish the above by using private and getter
In [12]:
export class LikeComponent {
  constructor(private _likesCount, private _isSelected){
  
  }

  onClick(){
    this._likesCount += (this._isSelected)? -1: 1;
    this._isSelected = !this._isSelected;
  }

  get likesCount(){
    return this._likesCount;
  }

  get isSelected(){
    return this._isSelected;
  }
}
Out[12]:
[Function: LikeComponent]

When we compile these files tsc *.ts, we get the following error

Accessors are only available when targeting ECMAScript 5 and higher

We need the compiler to target ES5, we can do this by tsc *.ts --target ES5

In Angular application, we don't really need these and we quite often use fields because the issue about changing the value of these field directly outside the class does not happen in Angular Applications.

In an Angular application, we bind an element on the view like a span, a paragraph etc to a field in the component. The value of these fields are displayed on the user interface so there is no code to directly modify that field.