JavaScript
Article

Understanding Component Architecture: Refactoring an Angular App

By Jurgen Van de Moere

Build an Angular CRUD App from the Ground Up

Angular Component Architecture

This article is part 2 of the SitePoint Angular 2+ Tutorial on how to create a CRUD App with the Angular CLI.


  1. Part 0— The Ultimate Angular CLI Reference Guide
  2. Part 1— Getting our first version of the Todo application up and running
  3. Part 2— Creating separate components to display a list of todo’s and a single todo
  4. Part 3— Update the Todo service to communicate with a REST API
  5. Part 4— Use Component Router to route to different components

In part one we learned how to get our Todo application up and running and deploy it to GitHub pages. This worked just fine, but unfortunately the whole app was crammed into a single component.

In this article (part two) we will examine a more modular component architecture. We will look at how to break this single component into a structured tree of smaller components that are easier to understand, re-use and maintain.

You don’t need to have followed part one of this tutorial, for part two to make sense. You can simply grab a copy of our repo, checkout the code from part one, and use that as a starting point. This is explained in more detail below.

A Quick Recap

So let’s look at what we covered in part one in slightly more detail. We learned how to:

  • initialize our Todo application using the Angular CLI
  • create a Todo class to represent individual todos
  • create a TodoDataService service to create, update and remove todos
  • use the AppComponent component to display the user interface
  • deploy our application to GitHub pages

The application architecture of part 1 looked like this:

Application Architecture

The components we discussed are marked with a red border.

In this second article we will delegate some of the work that AppComponent is doing to smaller components that are easier to understand, re-use and maintain.

We will create:

  • a TodoListComponent to display a list of todo’s
  • a TodoListItemComponent to display a single todo
  • a TodoListHeaderComponent to create a new todo
  • a TodoListFooterComponent to show how many todo’s are left

Application Architecture

By the end of this article, you will understand:

  • the basics of Angular component architecture
  • how you can pass data into a component using property bindings
  • how you can listen for events emitted by a component using event listeners
  • why it is a good practice to split components into smaller reusable components
  • the difference between smart and dumb components and why keeping components dumb is a good practice

So let’s get started!

Up and Running

The first thing you will need to follow along with this article is the latest version of the Angular CLI. You can install this with the following command:

npm install -g @angular/cli@latest

If you need to remove a previous version of of the Angular CLI, here’s how:

npm uninstall -g @angular/cli angular-cli
npm cache clean
npm install -g @angular/cli@latest

After that you’ll need a copy of the code from part one. This is available at https://github.com/sitepoint-editors/angular-todo-app. Each article in this series has a corresponding tag in the repository so you can switch back and forth between the different states of the application.

The code that we ended with in part one and that we start with in this article is tagged as part-1. The code that we end this article with is tagged as part-2.

You can think of tags like an alias to a specific commit id. You can switch between them using git checkout. You can read more on that here.

So, to get up and running (the the latest version of the Angular CLI installed) we would do:

git clone git@github.com:sitepoint-editors/angular-todo-app.git
cd angular-todo-app
npm install
git checkout part-1
ng serve

Then visit http://localhost:4200/. If all is well, you should see the working Todo app.

The Original AppComponent

Let’s open src/app/app.component.html and have a look at the AppComponent that we finished with in part one:

<section class="todoapp">
  <header class="header">
    <h1>Todos</h1>
    <input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
  </header>
  <section class="main" *ngIf="todos.length > 0">
    <ul class="todo-list">
      <li *ngFor="let todo of todos" [class.completed]="todo.complete">
        <div class="view">
          <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
          <label>{{todo.title}}</label>
          <button class="destroy" (click)="removeTodo(todo)"></button>
        </div>
      </li>
    </ul>
  </section>
  <footer class="footer" *ngIf="todos.length > 0">
    <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
  </footer>
</section>

and its corresponding class in src/app/app.component.ts:

import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [TodoDataService]
})
export class AppComponent {

  newTodo: Todo = new Todo();

  constructor(private todoDataService: TodoDataService) {
  }

  addTodo() {
    this.todoDataService.addTodo(this.newTodo);
    this.newTodo = new Todo();
  }

  toggleTodoComplete(todo: Todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  removeTodo(todo: Todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

  get todos() {
    return this.todoDataService.getAllTodos();
  }

}

Although our AppComponent works fine technically, keeping all code in one big component does not scale well and is not recommended.

Adding more features to our Todo application would make the AppComponent larger and more complex, making it harder to understand and maintain.

Therefore it is recommended to delegate functionality to smaller components. Ideally the smaller components should be configurable so that we don’t have to rewrite their code when the business logic changes.

For example: in part three of this series, we will update the TodoDataService to communicate with a REST API and we want to make sure that we will not have to change any of the smaller components when we refactor the TodoDataService.

If we look at the AppComponent template we can extract its underlying structure as:

<!-- header that lets us create new todo -->
<header></header>

<!-- list that displays todos -->
<ul class="todo-list">

    <!-- list item that displays single todo -->
    <li>Todo 1</li>

    <!-- list item that displays single todo -->
    <li>Todo 2</li>
</ul>

<!-- footer that displays statistics -->
<footer></footer>

If we translate this structure to Angular component names, we get:

<!-- TodoListHeaderComponent that lets us create new todo -->
<app-todo-list-header></app-todo-list-header>

<!-- TodoListComponent that displays todos -->
<app-todo-list>

    <!-- TodoListItemComponent that displays single todo -->
    <app-todo-list-item></app-todo-list-item>

    <!-- TodoListItemComponent that displays single todo -->
    <app-todo-list-item></app-todo-list-item>
</app-todo-list>

<!-- TodoListFooterComponent that displays statistics -->
<app-todo-list-footer></app-todo-list-footer>

Let’s see how we can use the power of Angular’s component driven development to make this happen.

A More Modular Component Architecture — Creating the TodoListHeaderComponent

Let’s start by creating the TodoListHeader component.

From the root of our project, we use Angular CLI to generate the component for us:

$ ng generate component todo-list-header

which generates the following files for us:

create src/app/todo-list-header/todo-list-header.component.css
create src/app/todo-list-header/todo-list-header.component.html
create src/app/todo-list-header/todo-list-header.component.spec.ts
create src/app/todo-list-header/todo-list-header.component.ts

and automatically adds TodoListHeaderComponent to the AppModule declarations:

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

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

// Automatically imported by Angular CLI
import { TodoListHeaderComponent } from './todo-list-header/todo-list-header.component';

@NgModule({
  declarations: [
    AppComponent,

    // Automatically added by Angular CLI
    TodoListHeaderComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Adding a component to the module declarations is required to make sure that all view templates in the module can use it the component. Angular CLI conveniently added TodoListHeaderComponent for us so we don’t have to add it manually.

If TodoListHeaderComponent was not in the declarations and we used it in a view template, Angular would throw the following error:

Error: Uncaught (in promise): Error: Template parse errors:
'app-todo-list-header' is not a known element:
1. If 'app-todo-list-header' is an Angular component, then verify that it is part of this module.
2. If 'app-todo-list-header' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.

To learn more about module declarations, make sure to check out the Angular Module FAQ.

Now that we have all files generated for our new TodoListHeaderComponent, we can move the <header> element from src/app/app.component.html to src/app/todo-list-header/todo-list-header.component.html:

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

and add the corresponding logic to src/app/todo-list-header/todo-list-header.component.ts:

import { Component, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';

@Component({
  selector: 'app-todo-list-header',
  templateUrl: './todo-list-header.component.html',
  styleUrls: ['./todo-list-header.component.css']
})
export class TodoListHeaderComponent {

  newTodo: Todo = new Todo();

  @Output()
  add: EventEmitter<Todo> = new EventEmitter();

  constructor() {
  }

  addTodo() {
    this.add.emit(this.newTodo);
    this.newTodo = new Todo();
  }

}

Instead of injecting the TodoDataService in our new TodoListHeaderComponent to save the new todo, we emit an add event and pass the new todo as an argument.

We already learned that the Angular template syntax allows us to attach a handler to an event. For example, the following code:

<input (keyup.enter)="addTodo()">

tells Angular to run the addTodo() method when the enter key was pressed inside the input. This works because the keyup.enter event is an event that is defined by the Angular framework.

However, we can also let a component emit its own custom events, by creating an EventEmitter and decorating it with the @Output() decorator:

import { Component, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';

@Component({
  // ...
})
export class TodoListHeaderComponent {

  // ...

  @Output()
  add: EventEmitter<Todo> = new EventEmitter();

  addTodo() {
    this.add.emit(this.newTodo);
    this.newTodo = new Todo();
  }

}

so we can assign an event handler in the view template using Angular’s event binding syntax:

<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>

Every time we call add.emit(value) in TodoListHeaderComponent, the onAddTodo($event) handler will be called and $event will be equal to value.

This decouples our TodoListHeaderComponent from the TodoDataService and allows the parent component to decide what needs to happen when a new todo is created.

When we update the TodoDataService to communicate with a REST API in part three, we won’t have to worry about the TodoListHeaderComponent because it is not even aware that the TodoDataService exists.

Smart vs Dumb Components

You may have already heard of smart and dumb components. Decoupling the TodoListHeaderComponent from the TodoDataService makes the TodoListHeaderComponent a dumb component. A dumb component is not aware of what happens outside of itself. It only receives input via property bindings and only emits output data as events.

Using smart and dumb components is a good practice. It greatly improves separation of concerns, making your application easier to understand and maintain. If your database or back-end API changes, you don’t have to worry about your dumb components. It also makes your dumb components more flexible, allowing you to reuse them more easily in different situations. If your application needs the same component twice, where one time it needs to write to a back-end database and another time it needs to write to an in-memory database, a dumb component allows you to accomplish exactly that.

So now that we created our TodoListHeaderComponent, let’s update our AppComponent template to use it:

<section class="todoapp">

  <!-- header is now replaced with app-todo-list-header -->
  <app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>

  <section class="main" *ngIf="todos.length > 0">
    <ul class="todo-list">
      <li *ngFor="let todo of todos" [class.completed]="todo.complete">
        <div class="view">
          <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
          <label>{{todo.title}}</label>
          <button class="destroy" (click)="removeTodo(todo)"></button>
        </div>
      </li>
    </ul>
  </section>

  <footer class="footer" *ngIf="todos.length > 0">
    <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
  </footer>

</section>

Notice how we use the onAddTodo($event) handler to capture add events that are emitted by the TodoListHeaderComponent when the user enters a new todo title:

<app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>

We add the onAddTodo() handler to the AppComponent class and remove the logic we no longer need:

import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [TodoDataService]
})
export class AppComponent {

  // No longer needed, now handled by TodoListHeaderComponent
  // newTodo: Todo = new Todo();

  constructor(private todoDataService: TodoDataService) {
  }

  // No longer needed, now handled by TodoListHeaderComponent
  // addTodo() {
  //   this.todoDataService.addTodo(this.newTodo);
  //   this.newTodo = new Todo();
  // }

  // Add new method to handle event emitted by TodoListHeaderComponent
  onAddTodo(todo: Todo) {
    this.todoDataService.addTodo(todo);
  }

  toggleTodoComplete(todo: Todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  removeTodo(todo: Todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

  get todos() {
    return this.todoDataService.getAllTodos();
  }

}

We now successfully moved the <header> element and all underlying logic from AppComponent to its own TodoListHeaderComponent.

The TodoListHeaderComponent is a dumb component and the AppComponent remains responsible for storing the todo using the TodoDataService.

Next, let’s tackle the TodoListComponent.

Creating the TodoListComponent

Let’s use Angular CLI again to generate our TodoListComponent:

$ ng generate component todo-list

which generates the following files for us:

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

and automatically adds TodoListComponent to the AppModule declarations:

// ...
import { TodoListComponent } from './todo-list/todo-list.component';

@NgModule({
  declarations: [
    // ...
    TodoListComponent
  ],
  // ...
})
export class AppModule { }

We now take the todo-list related HTML from src/app/app.component.html:

<section class="main" *ngIf="todos.length > 0">
  <ul class="todo-list">
    <li *ngFor="let todo of todos" [class.completed]="todo.complete">
      <div class="view">
        <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
        <label>{{todo.title}}</label>
        <button class="destroy" (click)="removeTodo(todo)"></button>
      </div>
    </li>
  </ul>
</section>

and move it to src/app/todo-list/todo-list.component.html:

<section class="main" *ngIf="todos.length > 0">
  <ul class="todo-list">
    <li *ngFor="let todo of todos" [class.completed]="todo.complete">
      <app-todo-list-item
        [todo]="todo"
        (toggleComplete)="onToggleTodoComplete($event)"
        (remove)="onRemoveTodo($event)"></app-todo-list-item>
    </li>
  </ul>
</section>

Notice that we have introduced a TodoListItemComponent which does not yet exist. However adding it to the template already allows us to explore what API the TodoListItemComponent should offer. This makes it easier for us to write the TodoListItemComponent in the next section because we now know what input and outputs we would expect TodoListItemComponent to have.

We pass in the todo item through the todo property using the [todo] input property syntax and attach event handlers to the events we would expect the TodoListItemComponent to emit, such as the toggleComplete event and the remove event.

Let’s open up src/app/todo-list/todo-list.component.ts and add the logic we need for our view template:

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Todo } from '../todo';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {

  @Input()
  todos: Todo[];

  @Output()
  remove: EventEmitter<Todo> = new EventEmitter();

  @Output()
  toggleComplete: EventEmitter<Todo> = new EventEmitter();

  constructor() {
  }

  onToggleTodoComplete(todo: Todo) {
    this.toggleComplete.emit(todo);
  }

  onRemoveTodo(todo: Todo) {
    this.remove.emit(todo);
  }

}

To further demonstrate the difference between smart and dumb components, we will also make the TodoListComponent a dumb component.

First we define an input property todos by marking it with the @Input() decorator. This allows us to inject the todos from the parent component.

Next, we define two output events, remove and toggleComplete, using the @Output() decorator. Notice how we set their type to EventEmitter<Todo> and assign them each a new EventEmitter instance.

The EventEmitter<Todo> type annotation is a TypeScript generic that tells TypeScript that both remove and toggleComplete are EventEmitter instances and that the values they emit are a Todo instance.

Finally, we define the onToggleTodoComplete(todo) and onRemoveTodo(todo) event handlers that we specified in our view using (toggleComplete)="onToggleTodoComplete($event)" and (remove)="onRemoveTodo($event)".

Notice how we use $event as the argument name in the view template and todo as the parameter name in the method definition. To access the payload (emitted value) of an event in an Angular template, we must always use $event as the argument name.

So by specifying (toggleComplete)="onToggleTodoComplete($event)" in our view template, we tell Angular to use the event payload as the first argument when calling the onToggleTodoComplete method, which will match the first parameter of the onToggleTodoComplete method, namely todo.

We know that the payload will be a todo instance, so we define the onToggleTodoComplete method as onToggleTodoComplete(todo: Todo), making our code easier to read, understand and maintain.

Finally, we define our event handlers to also emit a toggleComplete and remove event when they receive an incoming payload and specify the todo as the event payload.

In essence, we let TodoListComponent bubble up the events from its child TodoListItemComponent instances.

This allows us to handle the business logic outside of TodoListComponent, keeping TodoListComponent dumb, flexible and lightweight.

We also need to rename two methods in AppComponent to reflect this:

...
export class AppComponent {

  // rename from toggleTodoComplete
  onToggleTodoComplete(todo: Todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  // rename from removeTodo
  onRemoveTodo(todo: Todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

}

If we try to run our application at this stage, Angular will throw an error:

Unhandled Promise rejection: Template parse errors:
Can't bind to 'todo' since it isn't a known property of 'app-todo-list-item'.
1. If 'app-todo-list-item' is an Angular component and it has 'todo' input, then verify that it is part of this module.
2. If 'app-todo-list-item' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.

That’s because we have not created TodoListItemComponent yet.

So let’s do that next.

Creating the TodoListItemComponent

Again, we use Angular CLI to generate our TodoListItemComponent:

$ ng generate component todo-list-item

which generates the following files:

create src/app/todo-list-item/todo-list-item.component.css
create src/app/todo-list-item/todo-list-item.component.html
create src/app/todo-list-item/todo-list-item.component.spec.ts
create src/app/todo-list-item/todo-list-item.component.ts

and automatically adds TodoListItemComponent to the AppModule declarations:

// ...
import { TodoListItemComponent } from './todo-list-item/todo-list-item.component';

@NgModule({
  declarations: [
    // ...
    TodoListItemComponent
  ],
  // ...
})
export class AppModule { }

We can now move the original markup from inside the <li> to src/app/todo-list-item.component.html:

<div class="view">
  <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
  <label>{{todo.title}}</label>
  <button class="destroy" (click)="removeTodo(todo)"></button>
</div>

We don’t have to change anything to the markup, but we do have to make sure the events are handled properly, so let’s add the necessary code our TodoListItemComponent in src/app/todo-list-item/todo-list-item.component.ts:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';

@Component({
  selector: 'app-todo-list-item',
  templateUrl: './todo-list-item.component.html',
  styleUrls: ['./todo-list-item.component.css']
})
export class TodoListItemComponent {

  @Input() todo: Todo;

  @Output()
  remove: EventEmitter<Todo> = new EventEmitter();

  @Output()
  toggleComplete: EventEmitter<Todo> = new EventEmitter();

  constructor() {
  }

  toggleTodoComplete(todo: Todo) {
    this.toggleComplete.emit(todo);
  }

  removeTodo(todo: Todo) {
    this.remove.emit(todo);
  }

}

The logic is very similar to the logic we have in TodoListComponent.

First we define an @Input() so we can pass in a Todo instance:

@Input() todo: Todo;

Then we define the click event handlers for our template and emit a toggleComplete event when the checkbox is clicked and a remove event when the ‘X’ is clicked:

@Output()
remove: EventEmitter<Todo> = new EventEmitter();

@Output()
toggleComplete: EventEmitter<Todo> = new EventEmitter();

toggleTodoComplete(todo: Todo) {
  this.toggleComplete.emit(todo);
}

removeTodo(todo: Todo) {
  this.remove.emit(todo);
}

Notice how we don’t actually update or remove data. We merely emit events from the TodoListItemComponent when a user clicks a link to complete or remove a todo, making our TodoListItemComponent also a dumb component.

Remember how we attached event handlers to these events in the TodoListComponent template:

<section class="main" *ngIf="todos.length > 0">
  <ul class="todo-list">
    <li *ngFor="let todo of todos" [class.completed]="todo.complete">
      <app-todo-list-item
        [todo]="todo"
        (toggleComplete)="onToggleTodoComplete($event)"
        (remove)="onRemoveTodo($event)"></app-todo-list-item>
    </li>
  </ul>
</section>

The TodoListComponent then simply re-emits the events from TodoListItemComponent.

Bubbling up events from TodoListItemComponent through TodoListComponent allows us to keep both components dumb and ensures that we don’t have to update them when we refactor the TodoDataService to communicate with a REST API in part three of this series.

How cool is that!

Before we move on, let’s update our AppComponent template to use our new TodoListComponent:

<section class="todoapp">

  <app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>

  <!-- section is now replaced with app-todo-list -->
  <app-todo-list [todos]="todos" (toggleComplete)="onToggleTodoComplete($event)"
                 (remove)="onRemoveTodo($event)"></app-todo-list>

  <footer class="footer" *ngIf="todos.length > 0">
    <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
  </footer>

</section>

Finally, let’s tackle the TodoListFooterComponent.

Creating the TodoListFooterComponent

Again, from the root of our project, we use Angular CLI to generate TodoListFooterComponent for us:

$ ng generate component todo-list-footer

which generates the following files:

create src/app/todo-list-footer/todo-list-footer.component.css
create src/app/todo-list-footer/todo-list-footer.component.html
create src/app/todo-list-footer/todo-list-footer.component.spec.ts
create src/app/todo-list-footer/todo-list-footer.component.ts

and automatically adds TodoListFooterComponent to the AppModule declarations:

// ...
import { TodoListFooterComponent } from './todo-list-footer/todo-list-footer.component';

@NgModule({
  declarations: [
    // ...
    TodoListFooterComponent
  ],
  // ...
})
export class AppModule { }

Now we move the <footer> element from src/app/app.component.html to src/app/todo-list-footer/todo-list-footer.component.html:

<footer class="footer" *ngIf="todos.length > 0">
  <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
</footer>

and we add the corresponding logic to src/app/todo-list-footer/todo-list-footer.component.ts:

import { Component, Input } from '@angular/core';
import { Todo } from '../todo';

@Component({
  selector: 'app-todo-list-footer',
  templateUrl: './todo-list-footer.component.html',
  styleUrls: ['./todo-list-footer.component.css']
})
export class TodoListFooterComponent {

  @Input()
  todos: Todo[];

  constructor() {
  }

}

The TodoListFooterComponent does not require any methods. We only define the todos property using the @Input() decorator so we can pass in the todos using the todos property.

Finally, let’s update our AppComponent template to also use our new TodoListFooterComponent:

<section class="todoapp">
  <app-todo-list-header (add)="onAddTodo($event)"></app-todo-list-header>
  <app-todo-list [todos]="todos" (toggleComplete)="onToggleTodoComplete($event)"
                 (remove)="onRemoveTodo($event)"></app-todo-list>
  <app-todo-list-footer [todos]="todos"></app-todo-list-footer>
</section>

We have now successfully refactored our AppComponent to delegate its functionality to TodoListHeaderComponent, TodoListComponent and TodoListFooterComponent.

Before we wrap up this article, there is one more change we need to make.

Moving the TodoDataService Provider

In part 1, we registered the TodoDataService as a provider in AppComponent:

import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [TodoDataService]
})
export class AppComponent {

  newTodo: Todo = new Todo();

  constructor(private todoDataService: TodoDataService) {
  }

  addTodo() {
    this.todoDataService.addTodo(this.newTodo);
    this.newTodo = new Todo();
  }

  toggleTodoComplete(todo: Todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  removeTodo(todo: Todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

  get todos() {
    return this.todoDataService.getAllTodos();
  }

}

Although this works fine for our Todo application, the Angular team recommends adding application-wide providers to the root AppModule, instead of the root AppComponent.

Services registered in AppComponent are only available to AppComponent and its component tree. Services registered in AppModule are available to all components in the entire application.

If our Todo application would grow and introduce lazy loaded modules at some point, the lazy loaded modules would not be able to access the TodoDataService, because TodoDataService would only be available to AppComponent and its component tree and not within the entire application.

Therefore we remove TodoDataService as a provider in the AppComponent:

import {Component} from '@angular/core';
import {Todo} from './todo';
import {TodoDataService} from './todo-data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: []
})
export class AppComponent {

  newTodo: Todo = new Todo();

  constructor(private todoDataService: TodoDataService) {
  }

  addTodo() {
    this.todoDataService.addTodo(this.newTodo);
    this.newTodo = new Todo();
  }

  toggleTodoComplete(todo: Todo) {
    this.todoDataService.toggleTodoComplete(todo);
  }

  removeTodo(todo: Todo) {
    this.todoDataService.deleteTodoById(todo.id);
  }

  get todos() {
    return this.todoDataService.getAllTodos();
  }

}

and add it as a provider in the AppModule instead:

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

import { AppComponent } from './app.component';
import { TodoDataService } from './todo-data.service';
import { TodoListComponent } from './todo-list/todo-list.component';
import { TodoListFooterComponent } from './todo-list-footer/todo-list-footer.component';
import { TodoListHeaderComponent } from './todo-list-header/todo-list-header.component';
import { TodoListItemComponent } from './todo-list-item/todo-list-item.component';

@NgModule({
  declarations: [
    AppComponent,
    TodoListComponent,
    TodoListFooterComponent,
    TodoListHeaderComponent,
    TodoListItemComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [TodoDataService],
  bootstrap: [AppComponent]
})
export class AppModule { }

which concludes part two of this series.

Summary

In the first article, we learned how to:

  • initialize our Todo application using Angular CLI
  • create a Todo class to represent individual todo’s
  • create a TodoDataService service to create, update and remove todo’s
  • use the AppComponent component to display the user interface
  • deploy our application to GitHub pages

In this second article, we refactored AppComponent to delegate most of its work to:

  • a TodoListComponent to display a list of todo’s
  • a TodoListItemComponent to display a single todo
  • a TodoListHeaderComponent to create a new todo
  • a TodoListFooterComponent to show how many todo’s are left

In the process, we learned:

  • the basics of Angular component architecture
  • how to pass data into a component using property bindings
  • how to listen for events emitted by a component using event listeners
  • how splitting components into smaller reusable components makes our code easier to reuse and maintain
  • how we can use smart and dumb to make our life a whole lot easier when we need to refactor our application’s business logic

All code from this article is available at https://github.com/sitepoint-editors/angular-todo-app.

In the next part, we will refactor the TodoService to communicate with a REST API.

So stay tuned for part three!

This article was peer reviewed by Vildan Softic. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

  • Hinrich

    The link to the Github Repo is broken, I think it is this one https://github.com/sitepoint-editors/angular2-todo-app

    • Nilson Jacques

      Hey Hinrich, sorry about that. The links in the article should be working now (the repo was accidentally set to private). The link you mention only applies to the first article – for various reasons we had to create a new repo for this article. Part one will be updated with the newer repo in the near future. Thanks!

      • jvandemo

        Thank you, Hinrich, for reporting and thank you, Nilson, for fixing the issue. Much appreciated!

  • Jonathan

    Wonderful article !!! Thanks a lot for your job ! When do you think you’ll publish the part three ? I’m really exciting to view your REST API implementation.

    • jvandemo

      @Jonathan – Thank you for your kind words. It will take a couple of weeks before part 3 is published. It needs to be updated, reviewed and edited first before it can be published. But we are working hard on it. Thanks again for your feedback.

  • Aaron Te

    @jvandemo:disqus If u were to put the `list` in the `TodoList` component, how would you trigger it to fetch when its sibling `addTodo` adds an item? My use case is hoping to use `TodoList` anywhere else without worrying about always fetching the list from the parent I use it in. I hope my question is clear!

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.