Getting Started with Typescript Decorators

Getting Started with Typescript Decorators

Getting Started with Typescript Decorators, you'll understand decorators and use them to your Typescript projects. Typescript decorators have a range of use-cases that are primarily aimed at modifying or annotating class implementations. Decorators are a stage 2 Javascript proposal, but are available for us to use in Typescript today.

Decorators at a High Level

Decorators can be used to inject class properties, aid in collaborative development by annotating issues at certain points in your codebase — whether at the class level or method level — or simply log method arguments and return values in a non-obstructive way.

Decorators follow the decorator design pattern, and are often referred to as a type of meta programming. In the realm of Javascript, Angular have heavily adopted decorators into the framework. We have also seen packages such as lodsah adopt decorators to easily apply its functions to your code using the feature.

Note: Python has a mature decorator implementation that is heavily adopted today — the Flask web server being a great example whereby decorators are used to bind an endpoint to a function.

To experiment with Typescript decorators, enable them in tsconfig.json:

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

Or on the command line:

tsc --target ES5 --experimentalDecorators

Let’s jump straight into decorators to familiarise ourselves with the overall concept, before getting stuck into the implementations further down the article.

Where are decorators used?

You may have seen decorators being used in projects you have previously contributed to, noticing the @ symbol followed by a function name being applied to an object, class, class properties, or even method parameters.

And this is what can make decorators appear confusing; they are attached to a range of blocks of code, and where they are attached determines their implementation: In other words, your decorator function signatures will vary depending on what kind of object you attach them to. E.g:

  • Attaching a decorator to a class will give it access to the class prototype and its member properties.
  • Attaching a decorator to a class method will give it access to the method’s parameters, the metadata associated with the method object, as well as its class prototype.
  • Attaching a decorator to a class property will give it access to the name and value of that property, along with its class prototype.
  • Attaching a decorator to a class method parameter will give it access to that parameter’s index, name and value.

Concretely, we can attach decorators to:

// class definitions

@decorator
class MyComponent extends React.Component<Props, State> {
   ...
	 
// class methods

@decorator
private handleFormSubmit() {
   ...
// class method parameters

private handleFormSubmit(@decorator myParam: string) {
   ...

// accessors

@decorator
public myAccessor() {
   return this.privateProperty;
}

// class properties

@decorator
private static api_version: string;

Note: The above class definition example extends a React component. It is perfectly legal to use Typescript decorators with React and any other Javascript framework that Typescript can be applied to.

It is very unlikely that you will name your decorators @decorator. Maybe you will run into a decorator with a name not dissimilar to the following:

...
@deprecated
class MyComponent extends React.Component<Props> {
   ...
}

In this case, a @deprecated decorator has been applied to class MyComponent. Decorator names should be readable, short and self explanitory. But what exactly can @deprecated be used for?

Decorators are great as logging utilities

It would be handy if we documented within our codebase which classes or methods are planned to be phased out or no longer supported, with the intent of being completely removed in the next milestone. In this scenario, new developers on-boarding themselves into the project will know straight away that it is not worth enhancing this class.

In addition, if an issue has been posted relating to the class, then developers will know to avoid @deprecated classes, and could instead implement a new solution. Not only will the developer see @deprecated next to these soon-to-be removed pieces of code, the implementation of @deprecated could also console.log a message to the console, notifying the developer if the class is mistakenly called at runtime. Very handy.

But right now we are not supplying any data to these decorators. What if we wanted to pass in arguments? We can do so by utilising the concept of Decorator Factories.

Introducing Decorator Factories

Let’s expand the above example by combining @deprecated with another decorator, @inject:

@inject({
   api_version: '0.3.4'
})
@deprecated
class MyComponent extends React.Component<Props> {
   ...
}

As you can see, multiple decorators can be applied to one class, and each of which can accept arguments. The @inject decorator is now accepting an api_version argument, wrapped in an object.

But wait — we discussed above that a decorators’ signature is different depending on what we attach it to (class, method, property, etc…). Surely this argument hosting api_version is not a part of this decorators’ signature — and yes, that is correct.

To get around this, we wrap the decorator implementation within another function (that supports our desired arguments) which simply returns the decorator implementation. These are known as Decorator Factories, and can be implemented like so:

function inject(options: { api_version: string }) 
   return target => {
     ...
   }
}@inject({
   api_version: '0.3.4'
})
class MyComponent extends React.Component<Props> {
   ...
}

A lot has happened in the above example. Let’s break it down:

  • A decorator implementation is just another function. @inject is simply referring to the inject(options) function.
  • inject(options) returns the class decorator implementation, which just has one argument — target. Target will give us access to the entire class prototype.
  • Within our return function we can carry out any modifications or annotations we wish to make to the class.

Let’s complete this example by injecting the api_version into the class as a static property, and therefore completing our decorator implementation:

function inject(options: { api_version: string }) 
   return target => {
     target.apiVersion = options.api_version;
   }
}@inject({
   api_version: '0.3.4'
})
class MyComponent extends React.Component<Props> {
   static apiVersion: string;
   
   ...
}

This wraps up our first completed decorator! Injecting class properties is a useful way to add data to a class without disrupting its logic; In the above case I may need different classes to support specific API versions as to not break their implementations.

For the record, our @deprecated decorator could be implemented in the following way, without a Decorator Factory:

function deprecated(target) {
   console.log('this class is deprecated and will be removed in a future version of the app');
   console.log('@: ', target);
}

@deprecated
class MyComponent extends React.Component<Props> {
   ...

Note: Try to _console.log(target)_ now within your decorator implementation to see how your class is represented in the console.

Now, we have only covered one type of decorator implementation — the class decorator whereby target is the only parameter we have access to — but we will now want to familiarise ourselves will every implementation available to us. Let’s do that next.

Decorator Implementations

Here we will document the definitions of each type of decorator.

Classes

We have just covered class decorator implementations, that provide the class prototype (target) as the only parameter:

function classDecorator(options: any[]) {
   return target => {
      ...
   }
}

@classDecorator
class ...

Class properties

Class property decorators also present the class prototype as the first parameter, but also supply the name of the property we are accessing:

function prop(target, name) => {
      ...
   }
}

class MyComponent extends React.Component<Props> {
   @prop 
   public apiVersion: string

However, if the property is in fact a static property, the first parameter will present the class constructor function instead:

...
@prop
public static apiVersion: string;
...

Methods

Perhaps the most useful implementation, a method decorator will again provide us with the class prototype, but also two others: propertyKey and propertyDescriptor. Let’s use a Decorator Factory to represent this:

function methodDecorator(options: any[]) {
   return (
      target: MyComponent,
      propertyKey: string,
      propertyDescriptor: PropertyDescriptor
   ) => {
      ...
   }
}

class MyComponent extends React.Component {
   ...   
	 
	@methodDecorator
   handleSomething() {
      ...
   }
}

As the target parameter will be our class prototype, we could type it as the class component the decorator will be applied to, in the above case, MyComponent. The second parameter, propertyKey, will be a string containing the name of the method: handleSomething.

The third parameter — propertyDescriptor — is particularly useful here; it provides us with standard metadata (MDN) associated with the object: configurable, enumerable, value and writable, as well as get and set. We can overwrite any of these values if we intended to modify this function.

For example, if I wanted to toggle enumerability I could create the following decorator, providing a boolean in the Decorator Factory to determine enumerability which will then be applied to propertyDescriptor.enumerable:

function enumerable(enumerable: boolean) {
   return (
      target: MyComponent,
      propertyKey: string,
      propertyDescriptor: PropertyDescriptor
   ) => {
      propertyDescriptor.enumerable = enumerable;
   }
}

class MyComponent extends React.Component {
   ...   
	 
	 @enumerable(false)
   handleSomething() {
      ...
   }
}

This is great and demonstrates the power of decorators, but a (frankly) more interesting use case is to be able to log method arguments as they are called, as well as the returning value of that method. This gives us a comprehensive breakdown of what is happening at runtime, acting as a valuable debugging tool.

To do this we can refer to the original method definition using propertyDescriptor.value, before applying it to a new definition, adding our logging before and after the original:

function logger(
   target: MyComponent,
   propertyKey: string,
   propertyDescriptor: PropertyDescriptor
) => {
   
   //get original method
   let originalMethod = propertyDescriptor.value;
   
   //redefine descriptor value within own function block
   propertyDescriptor.value = function (...args: any[]) {      
	 
	 //log arguments before original function
      console.log(
        `${propertyKey} method called with args: 
         ${JSON.stringify(args)}`);       
				 
				 //attach original method implementation
       let result = originalMethod.apply(this, args);       
			 
			 //log result of method
       console.log(
         `${propertyKey} method return value: 
          ${JSON.stringify(result)}`);
   }
}
//apply decorator to a class method
...
@logger
handleSomething(name, value) {
   ...
}

In the above example we have used apply() to re-attach the original method to our new definition, with logging included.

Method Parameters

Now moving onto implementing method parameter decorators, we have access to the name and index of the parameters we decorate:

function decorator(
   class,
   name: string,
   index: int
) => {
   ...
}class MyComponent extends React.Component<Props> {
   ...
   
   private handleMethod(@decorator param1: string) {
      ...
   }
}

Again, this is useful for logging how arguments are passed and to which parameter they are associated with. They can also be used to amend arguments where you may want to modify the values being passed through, e.g. a @lowercase or @uppercase decorator.

For numbers, in the event you wish all your currency values to adhere to 2 decimal places, you could apply a @rounded decorator to these params as an alternative to polluting your boilerplate with primitive Javascript functions.

This sums up the current state of decorator implementations. At this stage you may be forming an idea of where you’d like decorators applied — is it worth to introduce additional complexity to your method signatures with parameter decorators? Or would you lean towards only using decorators to modify methods only — this will boil down to either personal taste or a collective team decision from project to project.

Importing Decorators

Keep your project structure intact by separating decorator functions from your class implementations, and import them via ES6 syntax:

import { logger, deprecated } from '../decorators';

Your decorator files should sit in src/ within a dedicated decorators/ folder:

src/
   /decorators
      index.ts

You may also wish to export a set of decorators into an npm package and use the package throughout your projects. This, in my experience, is the best way of managing decorators throughout a multi-project setup, scoping the package under your organisation:

import { logger, deprecated } from '@myorg/decorators';

In Summary

By now you should be able to implement decorators within your own Typescript projects.

My thoughts on decorators

In my opinion it is 100% worth taking some time to think about how decorators can be applied to your projects. We have already seen the benefits of decorators in other languages, and will undoubtedly be a part of Javascript and Typescript for the foreseeable future.

Although the underlying implementation of decorators may change as we come closer to a final Javascript proposal, most of these changes will be under the hood; the syntax adopted in this article will most likely stay the same — developers are now accustomed to this pattern, with other frameworks and languages adopting the same syntax rules.

As an open-source contributor I envisage decorators being used for Github related use-cases, such as issue tracking — for example, with an @issue decorator. Instead of browsing Github issues in the browser before relating them to the project codebase, it would be quicker and easier for developers to simply open the console and have a list of issues popping up with a URL to the Github issue, along with their related class or method, and file path to the file in question. Of course, you could have a boolean constant, const LOG_ISSUES = true, to toggle console output to any decorator.

Likewise, having the ability to browse a project and notice something like this directly in your IDE will give further clarity on what needs to be worked on:

@issue(850, 'good first issue')
class ProblematicClass extends React.Component<Props, State> {
   ...

Beyond collaborative development, utility decorators such as logging how long a method takes to process may be useful for API calls. A @memoize decorator would then be useful to cache resulting API results for future method calls.

Decorators definitely have their place in Typescript, and developers accustomed to them from elsewhere will undoubtedly be interested in using decorators in Javascript projects. For newcomers, decorators will offer an alternative design pattern as well as an opportunity to re-think how certain meta-progamming tasks are done in their projects.

TypeScript, Angular, Firebase & Angular Material Master class Tutorial

As the course progresses, you'll get familiar with: TypeScript, Angular Application Architecture, and Angular CLI. Angular Modules and Angular Components. Angular's Component LifeCycle Hooks....

As the course progresses, you'll get familiar with: TypeScript, Angular Application Architecture, and Angular CLI. Angular Modules and Angular Components. Angular's Component LifeCycle Hooks....

Learn More

Angular 7 (formerly Angular 2) - The Complete Guide

Learn and Understand AngularJS

Angular Crash Course for Busy Developers

The Complete Angular Course: Beginner to Advanced

Angular (Angular 2+) & NodeJS - The MEAN Stack Guide

Become a JavaScript developer - Learn (React, Node,Angular)

Angular (Full App) with Angular Material, Angularfire & NgRx

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript

Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript

Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript. This tutorial gets you off the ground with Angular. We are going to use the official CLI (command line) tool to generate boilerplate code.

Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript. This tutorial gets you off the ground with Angular. We are going to use the official CLI (command line) tool to generate boilerplate code.

1. Prerequisites

This tutorial is targeted to people familiar with JavaScript and HTML/CSS. You also will need:

  • Node.js up and running.
  • NPM (Node package manager) or Yarn installed.

You can verify by typing:

node --version
# v10.8.0
npm --version
# 6.2.0

If you get the versions Node 4.x.x and NPM 3.x.x. or higher you are all set. If not you have to get the latest versions.

Let’s move on to Angular. We are going to create a Todo app.

2. Understanding ng new

Angular CLI is the best way to get us started. We can download the tool and create a new project by running:

# install angular-cli globally
npm install -g @angular/[email protected]
# npm install -g @angular/cli # get latest

# Check angular CLI is installed
ng --version
# Angular CLI: 6.1.2

If the versions don’t match then you can remove previously installed angular CLI with the following commands:

npm uninstall -g @angular/cli
yarn global remove @angular/cli

Once you have the right version, do:

# create a new project
ng new Todos --style=scss

Note The last command takes some minutes. Leave it running and continue reading this tutorial.

The command ng new will do a bunch of things for us:

  1. Initialize a git repository
  2. Creates an package.json files with all the Angular dependencies.
  3. Setup TypeScript, Webpack, Tests (Jasmine, Protractor, Karma). Don’t worry if you don’t know what they are. We are going to cover them later.
  4. It creates the src folder with the bootstrapping code to load our app into the browser
  5. Finally, it does an npm install to get all the packages into node_modules.

Let’s run the app!

# builds the app and run it on port 9000
ng serve ---port 9000

Open your browser on http://localhost:9000/, and you should see “Loading…” and then it should switch to “Welcome to app!”. Awesome!

Now let’s dive into the src folder and get familiarized with the structure.

2.1 package.json

Open the package.json file and take a look at the dependencies. We have all the angular dependencies with the prefix @angular/.... Other dependencies are needed for Angular to run, such as RxJS, Zone.js, and some others. We are going to cover them in other posts.

2.2 src/index.html

We are building an SPA (single page application), so everything is going to be loaded into the index.html. Let’s take a look in the src/index.html. It’s pretty standard HTML5 code, except for two elements that are specific for our app:

  1. Initialize a git repository
  2. Creates an package.json files with all the Angular dependencies.
  3. Setup TypeScript, Webpack, Tests (Jasmine, Protractor, Karma). Don’t worry if you don’t know what they are. We are going to cover them later.
  4. It creates the src folder with the bootstrapping code to load our app into the browser
  5. Finally, it does an npm install to get all the packages into node_modules.

base href is needed for Angular routing to work correctly. We are going to cover Routing later.

<app-root> this is not a standard HTML tag. Our Angular App defines it. It’s an Angular component. More on this later.

2.3 src/main.ts

main.ts is where our application starts bootstrapping (loading). Angular can be used not just in browsers, but also on other platforms such as mobile apps or even desktop apps. So, when we start our application, we have to specify what platform we want to target. That’s why we import: platform-browser-dynamic. Notice that we are also importing the AppModule from ./app.

The most important line is:

platformBrowserDynamic().bootstrapModule(AppModule);

We are loading our AppModule into the browser platform. Now, let’s take a look at the ./app/app.module.tsdirectory.

2.4 App directory

The app directory contains the components used to mount the rest of the application. In there the <app-root> that we so in the index.html is defined. Let’s start with app.module

app.module.ts

We are going to be using this file often. The most important part is the metadata inside the @NgModule. There we have declarationsimportsproviders and bootstrap.

  • Node.js up and running.
  • NPM (Node package manager) or Yarn installed.

app.component.ts

AppComponent looks a little similar to the app module, but instead of @NgModule we have @Component. Again, the most important part is the value of the attributes (metadata). We have selectortemplateUrl and styleUrls:

  • Node.js up and running.
  • NPM (Node package manager) or Yarn installed.

Inside the AppComponent class you can define variables (e.g. title) that are used in the templates (e.g. Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript).

Let’s change the title from Welcome to Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript!to Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript. Also, remove everything else.
Test your changes running:

ng serve ---port 9000

You should see the new message.

[changes diff]

3. Creating a new Component with Angular CLI

Let’s create a new component to display the tasks. We can quickly create by typing:

ng generate component todo

This command will create a new folder with four files:

create src/app/todo/todo.component.css
create src/app/todo/todo.component.html
create src/app/todo/todo.component.spec.ts
create src/app/todo/todo.component.ts

And it will add the new Todo component to the AppModule:

UPDATE src/app/app.module.ts

Go ahead and inspect each one. It will look similar to the app components. Let ‘s add our new component to the App component.

[changes diff]

Go to src/app/app.component.html, and replace everything with:

src/app/app.component.html

<app-todo></app-todo>

If you have ng serve running, it should automatically update and show todo works!

[changes diff]

4. Todo Template

“todo works!” is not useful. Let’s change that by adding some HTML code to represent our todo tasks. Go to the src/app/todo/todo.component.html file and copy-paste this HTML code:

<section class="todoapp">

  <header class="header">
    <h1>Todo</h1>
    <input class="new-todo" placeholder="What needs to be done?" autofocus>
  </header>

  <!-- This section should be hidden by default and shown when there are todos -->
  <section class="main">

    <ul class="todo-list">
      <!-- These are here just to show the structure of the list items -->
      <!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
      <li class="completed">
        <div class="view">
          <input class="toggle" type="checkbox" checked>
          <label>Install angular-cli</label>
          <button class="destroy"></button>
        </div>
        <input class="edit" value="Create a TodoMVC template">
      </li>
      <li>
        <div class="view">
          <input class="toggle" type="checkbox">
          <label>Understand Angular2 apps</label>
          <button class="destroy"></button>
        </div>
        <input class="edit" value="Rule the web">
      </li>
    </ul>
  </section>

  <!-- This footer should hidden by default and shown when there are todos -->
  <footer class="footer">
    <!-- This should be `0 items left` by default -->
    <span class="todo-count"><strong>0</strong> item left</span>
    <!-- Remove this if you don't implement routing -->
    <ul class="filters">
      <li>
        <a class="selected" href="#/">All</a>
      </li>
      <li>
        <a href="#/active">Active</a>
      </li>
      <li>
        <a href="#/completed">Completed</a>
      </li>
    </ul>
    <!-- Hidden if no completed items are left ↓ -->
    <button class="clear-completed">Clear completed</button>
  </footer>
</section>

The above HTML code has the general structure about how we want to represent our tasks. Right now it has hard-coded todo’s. We are going to slowly turn it into a dynamic app using Angular data bindings.

[changes diff]

Next, let’s add some styling!

5. Styling the todo app

We are going to use a community maintained CSS for Todo apps. We can go ahead and download the CSS:

npm install --save todomvc-app-css

This will install a CSS file that we can use to style our Todo app and make it look nice. In the next section, we are going to explain how to use it with the angular-cli.json.

6. Adding global styles to angular.json

angular.json is a special file that tells the Angular CLI how to build your application. You can define how to name your root folder, tests and much more. What we care right now, is telling the angular CLI to use our new CSS file from the node modules. You can do it by adding the following line into the styles array:

"architect": {
  "build": {
    "options": {
      "styles": [
        "src/styles.scss",
        "node_modules/todomvc-app-css/index.css"
      ],
      "scripts": []

If you stop and start ng serve, then you will notice the changes.

We have the skeleton so far. Now we are going to make it dynamic and allow users to add/remove/update/sort tasks. We are going to do two versions one serverless and another one using a Node.js/Express server. We are going to be using promises all the time, so when we use a real API, the service is the only one that has to change.

[changes diff]

7. Todo Service

Let’s first start by creating a service that contains an initial list of tasks that we want to manage. We are going to use a service to manipulate the data. Let’s create the service with the CLI by typing:

ng g service todo/todo

This will create two files:

create src/app/todo/todo.service.spec.ts
create src/app/todo/todo.service.ts

[changes diff]

8. CRUD Functionality

For enabling the create-read-update-delete functionality, we are going to be modifying three files:

  • Node.js up and running.
  • NPM (Node package manager) or Yarn installed.

Let’s get started!

8.1 READ: Get all tasks

Let’s modify the todo.service to be able to get tasks:

import { Injectable } from '@angular/core';

const TODOS = [
  { title: 'Install Angular CLI', isDone: true },
  { title: 'Style app', isDone: true },
  { title: 'Finish service functionality', isDone: false },
  { title: 'Setup API', isDone: false },
];

@Injectable({
  providedIn: 'root'
})
export class TodoService {

  constructor() { }

  get() {
    return new Promise(resolve => resolve(TODOS));
  }
}

Now we need to change our todo component to use the service that we created.

import { Component, OnInit } from '@angular/core';
import { TodoService } from './todo.service';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.scss'],
  providers: [TodoService]
})
export class TodoComponent implements OnInit {
  private todos;
  private activeTasks;

  constructor(private todoService: TodoService) { }

  getTodos(){
    return this.todoService.get().then(todos => {
      this.todos = todos;
      this.activeTasks = this.todos.filter(todo => todo.isDone).length;
    });
  }

  ngOnInit() {
    this.getTodos();
  }
}

The first change is importing our TodoService and adding it to the providers. Then we use the constructor of the component to load the TodoService. While we inject the service, we can hold a private instance of it in the variable todoService. Finally, we use it in the getTodos method. This will make a variable todos available in the template where we can render the tasks.

Let’s change the template so we can render the data from the service. Go to the todo.component.html and change what is inside the <ul class="todo-list"> ... </ul> for this one:

<ul class="todo-list">
  <li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >
    <div class="view">
      <input class="toggle" type="checkbox" [checked]="todo.isDone">
      <label>{{todo.title}}</label>
      <button class="destroy"></button>
    </div>
    <input class="edit" value="{{todo.title}}">
  </li>
</ul>

Also change the 32 in the template from:

<span class="todo-count"><strong>0</strong> item left</span>

replace it with:

<span class="todo-count"><strong>{{activeTasks}}</strong> item left</span>

When your browser updates you should have something like this:

Now, let’s go over what we just did. We can see that we added new data-binding into the template:

  • Node.js up and running.
  • NPM (Node package manager) or Yarn installed.

[changes diff]

8.2 CREATE: using the input form

Let’s start with the template this time. We have an input element for creating new tasks. Let’s listen to changes in the input form and when we click enter it creates the TODO.

<input class="new-todo"
       placeholder="What needs to be done?"
       [(ngModel)]="newTodo"
       (keyup.enter)="addTodo()"
       autofocus>

Notice that we are using a new variable called newTodo and method called addTodo(). Let’s go to the controller and give it some functionality:

private newTodo;

addTodo(){
  this.todoService.add({ title: this.newTodo, isDone: false }).then(() => {
    return this.getTodos();
  }).then(() => {
    this.newTodo = ''; // clear input form value
  });
}

First, we created a private variable that we are going to use to get values from the input form. Then we created a new todo using the todo service method add. It doesn’t exist yet, so we are going to create it next:

add(data) {
  return new Promise(resolve => {
    TODOS.push(data);
    resolve(data);
  });
}

The above code adds the new element into the todos array and resolves the promise. That’s all. Go ahead a test it out creating a new todo element.

You might get an error saying:

Can't bind to 'ngModel' since it isn't a known property of 'input'

To use the two-way data binding you need to import FormsModule in the app.module.ts. So let’s do that.

import { FormsModule } from '@angular/forms';

// ...

@NgModule({
  imports: [
    // ...
    FormsModule
  ],
  // ...
})

Now it should add new tasks to the list!

[changes diff]

8.3 UPDATE: on double click

Let’s add an event listener to double-click on each todo. That way, we can change the content. Editing is tricky since we need to display an input form. Then when the user clicks enter it should update the value. Finally, it should hide the input and show the label with the updated value. Let’s do that by keeping a temp variable called editing which could be true or false.

<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone, editing: todo.editing}" >
  <div class="view">
    <input class="toggle" type="checkbox" [checked]="todo.isDone">
    <label (dblclick)="todo.editing = true">{{todo.title}}</label>
    <button class="destroy"></button>
  </div>
  <input class="edit"
         #updatedTodo
         [value]="todo.title"
         (blur)="updateTodo(todo, updatedTodo.value)"
         (keyup.escape)="todo.editing = false"
         (keyup.enter)="updateTodo(todo, updatedTodo.value)">
</li>

Notice that we are adding a local variable in the template #updateTodo. Then we use it to get the value like updateTodo.value and pass it to a function. We want to update the variables on blur (when you click somewhere else) or on enter. Let’s add the function that updates the value in the component.

Also, notice that we have a new CSS class applied to the element called editing. This is going to take care through CSS to hide and show the input element when needed.

updateTodo(todo, newValue) {
  todo.title = newValue;
  return this.todoService.put(todo).then(() => {
    todo.editing = false;
    return this.getTodos();
  });
}

We update the new todo’s title, and after the service has processed the update, we set editing to false. Finally, we reload all the tasks again. Let’s add the put action on the service.

put(changed) {
  return new Promise(resolve => {
    const index = TODOS.findIndex(todo => todo === changed);
    TODOS[index].title = changed.title;
    resolve(changed);
  });
}

Now, we can edit tasks! Yay!

[changes diff]

8.4 DELETE: clicking X

This is like the other actions. We add an event listenter on the destroy button:

<button class="destroy" (click)="destroyTodo(todo)"></button>

Then we add the function to the component:

destroyTodo(todo) {
  this.todoService.delete(todo).then(() => {
    return this.getTodos();
  });
}

and finally, we add the method to the service:

delete(selected) {
  return new Promise(resolve => {
    const index = TODOS.findIndex(todo => todo === selected);
    TODOS.splice(index, 1);
    resolve(true);
  });
}

Now test it out in the browser!

[changes diff]

9. Routing and Navigation

It’s time to activate the routing. When we click on the active button, we want to show only the ones that are active. Similarly, we want to filter by completed. Additionally, we want to the filters to change the route /active or /completed URLs.

In AppModule, we need to add the router library and define the routes as follows:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { Routes, RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { TodoComponent } from './todo/todo.component';

const routes: Routes = [
  { path: ':status', component: TodoComponent },
  { path: '**', redirectTo: '/all' }
];

@NgModule({
  declarations: [
    AppComponent,
    TodoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(routes)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

First, we import the routing library. Then we define the routes that we need. We could have said path: 'active', component: TodoComponent and then repeat the same for completed. But instead, we define a parameter called :status that could take any value (allcompletedactive). Any other value path we are going to redirect it to /all. That’s what the ** means.

Finally, we add it to the imports. So the app module uses it. Since the AppComponent is using routes, now we need to define the <router-outlet>. That’s the place where the routes are going to render the component based on the path (in our case TodoComponent).

Let’s go to app/app.component.html and replace <app-todo></app-todo> for <router-outlet></router-outlet>:

<router-outlet></router-outlet>

Test the app in the browser and verify that now the URL is by default [http://localhost:9000/all](http://localhost:9000/all "http://localhost:9000/all").

[changes diff]

9.1 Using routerLink and ActivatedRoute

routerLink is the replacement of href for our dynamic routes. We have set it up to be /all/complete and /active. Notice that the expression is an array. You can pass each part of the URL as an element of the collection.

<ul class="filters">
  <li>
    <a [routerLink]="['/all']" [class.selected]="path === 'all'">All</a>
  </li>
  <li>
    <a [routerLink]="['/active']" [class.selected]="path === 'active'">Active</a>
  </li>
  <li>
    <a [routerLink]="['/completed']" [class.selected]="path === 'completed'">Completed</a>
  </li>
</ul>

What we are doing is applying the selected class if the path matches the button. Yet, we haven’t populate the the path variable yet. So let’s do that:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { TodoService } from './todo.service';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.scss'],
  providers: [TodoService]
})
export class TodoComponent implements OnInit {
  private todos;
  private activeTasks;
  private newTodo;
  private path;

  constructor(private todoService: TodoService, private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.subscribe(params => {
      this.path = params['status'];
      this.getTodos();
    });
  }

  /* ... */
}

We added ActivatedRoute as a dependency and in the constructor. ActivatedRoute gives us access to the all the route params such as path. Notice that we are using it in the NgOnInit and set the path accordantly.

Go to the browser and check out that the URL matches the active button. But, it doesn’t filter anything yet. Let’s fix that.

[changes diff]

9.2 Filtering data based on the route

To filter todos by active and completed, we need to pass a parameter to the todoService.get.

ngOnInit() {
  this.route.params.subscribe(params => {
    this.path = params['status'];
    this.getTodos(this.path);
  });
}

getTodos(query = ''){
  return this.todoService.get(query).then(todos => {
    this.todos = todos;
    this.activeTasks = this.todos.filter(todo => todo.isDone).length;
  });
}

We added a new parameter query, which takes the path (active, completed or all). Then, we pass that parameter to the service. Let’s handle that in the service:

get(query = '') {
  return new Promise(resolve => {
    let data;

    if (query === 'completed' || query === 'active'){
      const isCompleted = query === 'completed';
      data = TODOS.filter(todo => todo.isDone === isCompleted);
    } else {
      data = TODOS;
    }

    resolve(data);
  });
}

So we added a filter by isDone when we pass either completed or active. If the query is anything else, we return all the todos tasks. That’s pretty much it, test it out!

[changes diff]

10. Clearing out completed tasks

One last UI functionality, clearing out completed tasks button. Let’s first add the click event on the template:

<button class="clear-completed" (click)="clearCompleted()">Clear completed</button>

We referenced a new function clearCompleted that we haven’t create yet. Let’s create it in the TodoComponent:

clearCompleted() {
  this.todoService.deleteCompleted().then(() => {
    return this.getTodos();
  });
}

In the same way we have to create deleteCompleted in the service:

deleteCompleted() {
  return new Promise(resolve => {
    todos = todos.filter(todo => !todo.isDone);
    resolve(todos);
  });
}

We use the filter to get the active tasks and replace the todos array with it.

That’s it we have completed all the functionality.

[changes diff]

11. Deploying the app

You can generate all your assets for production running this command:

ng build --prod

It will minify and concatenate the assets for serving the app faster.

If you want to deploy to a Github page you can do the following:

ng build --prod --output-path docs --base-href "/angular-todo-app/"

Replace /angular-todo-app/ with the name of your project name. Finally, go to settings and set up serving Github pages using the /docs folder:

12. Troubleshooting

If when you compile for production you get an error like:

The variable used in the template needs to be declared as "public". Template is treated as a separate Typescript class.

ERROR in src/app/todo/todo.component.html(7,8): : Property 'newTodo' is private and only accessible within class 'TodoComponent'.
src/app/todo/todo.component.html(19,11): : Property 'todos' is private and only accessible within class 'TodoComponent'.
src/app/todo/todo.component.html(38,38): : Property 'activeTasks' is private and only accessible within class 'TodoComponent'.
src/app/todo/todo.component.html(41,36): : Property 'path' is private and only accessible within class 'TodoComponent'.
src/app/todo/todo.component.html(44,39): : Property 'path' is private and only accessible within class 'TodoComponent'.
src/app/todo/todo.component.html(47,42): : Property 'path' is private and only accessible within class 'TodoComponent'.
src/app/todo/todo.component.html(7,8): : Property 'newTodo' is private and only accessible within class 'TodoComponent'.

Then you need to change private to public like this. This is because the Template in Angular is treated like a separate class.

That’s all folks!

==================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Angular 8 (formerly Angular 2) - The Complete Guide

☞ Learn and Understand AngularJS

☞ The Complete Angular Course: Beginner to Advanced

☞ Angular Crash Course for Busy Developers

☞ Angular Essentials (Angular 2+ with TypeScript)

☞ Angular (Full App) with Angular Material, Angularfire & NgRx

☞ Angular & NodeJS - The MEAN Stack Guide

How to Post FormData with Angular 9, TypeScript and HttpClient

How to Post FormData with Angular 9, TypeScript and HttpClient

In this Angular 9 tutorial, we'll learn how to send post multi-part form data to a web server using TypeScript, Angular 9, HttpClient and FormData. Learn how to work with FormData in Angular 9 and TypeScript and how to post it to a web server via a POST request and HttpClient. Learn how to Post FormData (multipart/form-data) with Angular 9/8, TypeScript and HttpClient

In this quick howto tutorial, you'll learn how to work with FormData in Angular 9 and TypeScript and how to post it to a web server via a POST request and HttpClient.

One of the most important aspects of web development is forms as they allow you to collect data from users and send it to servers.

There are various ways to work with forms in JavaScript and HTML. Also different frameworks (such as Angular) added other ways to handle forms.

In this tutorial, we'll be looking at FormData, A browser API for handling forms data just like its name suggests. This API provides methods and properties that enable to have access and work with form elements and their values in a straightforward way.

It's particularly helpful if you are working with client side frameworks like Angular as it allows you to easily prepare form data to be sent with POST HTTP requests.

Note: You can think of FormData as a reprsentation of an HTML form in JavaScript instead of HTML.

You also can create a FormData instance from an HTML form

The FormData API allows you to create a set of key/value elements that correspond to form fields and their values. This can be then sent to the server using Angular HttpClient.

How to use FormData in Angular 9?

Let's now see an example of how you can create a FormData instance and send it with HttpClient POST in Angular 9.

Note: We assume that you have a server running at the http://localhost:3000 address with an /upload that accepts POST requests for uploading files in your server.

Provided that you have created an Angular 9 project with Angular CLI, navigate to your project's root folder and run the following command to generate a component that we'll be working with:

$ ng generate component upload

Open the src/app/upload/upload.component.html file and add the following form:

<h1>Angular 9 FormData (multipart/data-form) Example</h1>
<div>
    <form [formGroup] = "uploadForm" (ngSubmit)="onSubmit()">      
      <div>
        <input type="file" name="profile" (change)="onFileSelect($event)" />
      </div>
      <div>
        <button type="submit">Upload</button>
      </div>
    </form>
  </div>
</div>

Next, open the src/app/upload/upload.component.ts file and start by importing the necessary APIs:

import { FormBuilder, FormGroup } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

We import FormBuilder and FormGroup from the @angular/forms package which are necessary to create a reactive form in Angular. We also import HttpClient that will be used to send data to the server.

Note: Make sure to import ReactiveFormsModule and HttpClientModule in your main module application which exists in the src/app/app.module.ts file and add them to the imports array.

Using HttpClient calls directly from your components is against the separation of concerns principle but this is just a simple example. Typically, you would need to create a service and make HttpClient from the service.

Next define the SERVER_URL and uploadForm variables in your component:

export class UploadComponent implements OnInit {

  SERVER_URL = "http://localhost:3000/upload";
  uploadForm: FormGroup;  

Next, import and inject HttpClient and FormBuilder:

export class UploadComponent implements OnInit {
  constructor(private formBuilder: FormBuilder, private httpClient: HttpClient) { }

Next, create a reactive form in ngOnInit() method of the component which gets called when the component is initialized:

  ngOnInit() {
    this.uploadForm = this.formBuilder.group({
      profile: ['']
    });
  }

Next, let's add the onFileSelect() method which gets called when a file is selected by the user:

  onFileSelect(event) {
    if (event.target.files.length > 0) {
      const file = event.target.files[0];
      this.uploadForm.get('profile').setValue(file);
    }
  }

We simply check if at least one file is selected and we set the profile field of uploadForm to the selected file.

Finally, let's see how we can use FormData to send multipart/form-data to our server. Let's define the onSubmit() method:

  onSubmit() {
    const formData = new FormData();
    formData.append('file', this.uploadForm.get('profile').value);

    this.httpClient.post<any>(this.SERVER_URL, formData).subscribe(
      (res) => console.log(res),
      (err) => console.log(err)
    );
  }

We simply create an instance of FormData, next we add fields with their values using the append() method of FormData. In our example, we only add a field named file which holds the value the selected file. Finally we use the post() method of HttpClient to send the form data to the server.

For reference, FormData provides the following methods for working with form data:

  • The FormData.append() appends a new value for an existing key, or adds the key if it does not exist.
  • The FormData.delete() method deletes a key/value pair from a FormData object.
  • The FormData.entries()method provides an iterator for going through all key/value pairs of the instance.
  • The FormData.get() method Returns the first value associated with a given key from within a FormData object.
  • The FormData.getAll() method provides an array of all the values associated with a specific key.
  • The FormData.has() methods provides a boolean indicating whether a FormData instance contains a specific key.
  • The FormData.keys() method provides an iterator for going through all the keys contained in the form instance.
  • The FormData.set()method sets a new value for an existing key inside a FormData object, or adds the key/value if it does not exist.
  • The FormData.values() method provides an iterator for going through all values contained in this object.
Conclusion

In this tutorial, we've seen how to send post multi-part form data to a server using TypeScript, Angular 9, HttpClient and FormData.