Lesson Wednesday

Our To Do List has grown significantly over the past few lessons. Almost all of our functionality resides in our single root component. After following along with the previous lessons, our root component's file should look like this:

app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1>To Do List for {{month}}/{{day}}/{{year}}</h1>
      <h3>{{currentFocus}}</h3>
      <ul>
      <li [class]="priorityColor(currentTask)" (click)="isDone(currentTask)" *ngFor="let currentTask of tasks">{{currentTask.description}} <button (click)="editTask(currentTask)">Edit!</button></li>
      </ul>
      <hr>
      <div>
        <div *ngIf="selectedTask">
          <h3>{{selectedTask.description}}</h3>
          <p>Task Complete? {{selectedTask.done}}</p>
          <hr>
          <h3>Edit Task</h3>
          <label>Enter Task Description:</label>
          <input [(ngModel)]="selectedTask.description">
          <label>Enter Task Priority (1-3):</label><br>
          <input type="radio" [(ngModel)]="selectedTask.priority" [value]="1">1 (Low Priority)<br>
          <input type="radio" [(ngModel)]="selectedTask.priority" [value]="2">2 (Medium Priority)<br>
          <input type="radio" [(ngModel)]="selectedTask.priority" [value]="3">3 (High Priority)
          <button (click)="finishedEditing()">Done</button>
        </div>
      </div>
    </div>
  `
})

export class AppComponent {
  currentFocus: string = 'Angular Homework';
  currentTime = new Date();
  month: number = this.currentTime.getMonth() + 1;
  day: number = this.currentTime.getDate();
  year: number = this.currentTime.getFullYear();
  tasks: Task[] = [
    new Task('Finish weekend Angular homework for Epicodus course', 3),
    new Task('Begin brainstorming possible JavaScript group projects', 2),
    new Task('Add README file to last few Angular repos on GitHub', 2)
  ];
  selectedTask = null;

  editTask(clickedTask) {
    this.selectedTask = clickedTask;
  }

  isDone(clickedTask: Task) {
    if(clickedTask.done === true) {
      alert("This task is done!");
    } else {
      alert("This task is not done. Better get to work!");
    }
  }

  priorityColor(currentTask){
    if (currentTask.priority === 3){
      return "bg-danger";
    } else if (currentTask.priority === 2) {
      return  "bg-warning";
    } else {
      return "bg-info";
    }
  }

  finishedEditing() {
    this.selectedTask = null;
  }

}

export class Task {
  public done: boolean = false;
  constructor(public description: string, public priority: number) { }
}

However, placing everything in the root component is not best practice. Now that our application has grown, we'll organize our code into multiple components, each with their own distinct responsibility and purpose.

In this lesson we'll discuss best practices for structuring an Angular 2 application, including how to determine when to use multiple components, how components work together in an application, and how to plan an application's component structure. We'll create a plan for re-organizing our To Do List application into multiple components together. Then, we'll create the first of our new components in this lesson. (Don't worry, we'll build out the rest together in upcoming lessons.)

Angular Application Structure

Angular 2 applications are a series of components. They begin with a top-level root component. The root component houses all other components in the application.

Components that reside in another component are often known as child components or nested components.

These child components may also contain their own child components. This allows us to divide our code based on its functionality and re-use it throughout the application.

When to Create a Component

When should we make a new component and when should we add new features to an existing component? When do we refactor one large component (like our To Do List's root component) into multiple components? There are several important best practices to follow:

Components Should be Focused on One Job

Ideally, components should have one sole responsibility. Common responsibilities include displaying items, creating new items, or modifying existing items.

Separating our application into components with clear, distinct purposes makes it easier to debug and makes it easier for other developers to jump into our code base.

Components Can Be Reused

Additionally, don't forget that components can be reused throughout an application. Will your app require the same functionality and content in multiple spots? That's an indication a component should be used.

For instance, an address book application that lists a user's contacts may want each entry to have the same appearance and functionality: a detail form to appear when clicked, uniform styling and appearance, a 'delete' button, and so on. These pieces of functionality and display can be packaged in a single component and then repeated for each Contact in the address book.

Planning an Application

Now that we have the skillset necessary to create larger, more complex Angular applications, you should consider the structure of your application before beginning to code.

This next section will address how to plan out an Angular application's component structure. We'll create a plan to refactor our To Do List into multiple components. Then we'll create the first of these new components.

User Stories

Let's consider our user stories. Remember, user stories are essentially a list of requested features.

For instance, here are the three primary user stories for our To Do List:

  • As a user, I want to be able to see a list of all the tasks I have to do.
  • As a user, I want to be able to edit an individual task if I need to.
  • As a user, I want to be able to create new tasks to add to my list.

Even though we've actually already started our To Do List, let's walk through planning our application's structure based on user stories. This will help us determine how to refactor our To Do List and provide guidance on how to plan new applications in the future.

Requirements

First off, we already know several parts of our application are required. We'll need them no matter what.

In addition to the configuration files detailed in the two Angular Setup lessons, every application requires a root module and root component.

The root component resides in the root module. The root component will contain our child components. Its sole responsibility is housing nested components and passing them data they require.

This is the basic structure of our application:

component-planning-diagram-stage-one

Additional Components

What other components will we need? Let's walk through our user stories and determine how to organize this functionality into components.

Listing Tasks

The first user story reads "As a user, I want to be able to see all my tasks."

Remember, each component should have a distinct, dedicated responsibility. Our root component will be responsible for housing child components and passing along data. That's its dedicated responsibility. We can't rely on it to list Tasks, too.

Instead, we'll create a new TaskListComponent to handle displaying our list of Tasks. It will reside directly within our root component.

After implementing a TaskListComponent, our application structure will look like this:

component-planning-diagram-stage-two

Editing Tasks

Let's consider the next user story: "As a user, I want to be able to edit existing task descriptions and create new tasks."

While editing a Task requires multiple steps (displaying an edit form, making changes and hiding the form again), this can still be considered a single responsibility in the application.

Therefore, we'll need a dedicated component to handle it. We'll create an EditTaskComponent to manage the process of editing tasks. It will also reside in the root component:

component-planning-diagram-stage-3

Creating New Tasks

The final user story above reads: "As a user, I want to be able to create new Tasks to add to my list."

We haven't added the ability to create a Task through our application yet. Our Tasks are still hard-coded. We'll change this in a future lesson. However, we know we'll need a component for creating Tasks as well.

Let's plan on calling this the NewTaskComponent. It'll contain a form to enter details for a new Task. It will also reside directly in the root component:

component-planning-diagram-stage-four

We'll eventually add other features like filtering Tasks based on their priority and marking them as done, but for now we'll focus on these three new components.

Planning the structure of an Angular application like this can be beneficial. While your plan may change as you develop, knowing the components you'll require and where they'll be located can make the development process smoother. Moving forward, make sure to draft the component structure of new applications before writing code.

Organizing an Application into Nested Components

Now that we have a plan, we know we'll need the following new components for our To Do List app:

  • TaskListComponent: List all Tasks.
  • EditTaskComponent: Manage editing and updating Tasks, including hiding and showing the edit form.
  • NewTaskComponent: Add functionality that will allow us to add new Tasks directly through our application.

In this next section, we'll build only the first of these new components together, the TaskListComponent. Upcoming lessons will walk through the creation of the other two components.

Child Component Setup

Let's create a new file in the app folder called task-list.component.ts.

Component file names should always be all lowercase and describe the purpose of the component. Multiple-word names should include hyphens between words. Every component file should end in .component.ts.

As you may remember from Angular Setup: Root Component, Root Module, each new component requires three things:

  • An import statement at the top of the file that imports component code from the Angular framework.
  • The @Component decorator, which tells Angular that we're creating a new component. This also contains the annotation, which includes thetemplate and selector.
  • A class declaration, sometimes referred to as the "back-end" of the component, to handle behavior. Any methods, property definitions, or other functionality will reside here.

Import Statements

First, we'll import the Component code:

app/task-list.component.ts
import { Component } from '@angular/core';

Then, we'll create our decorator, including the selector and a blank template:

app/task-list.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'task-list',
  template: `
  `
})

We've defined our selector as task-list so that we can later instruct Angular where to render this component using <task-list></task-list> tags.

Finally, we'll declare our component's class:

app/task-list.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'task-list',
  template: `
  `
})

export class TaskListComponent {

}

Component class names should be UpperCamelCase and end in the word Component. The class declaration always starts with export, so that its code is available to other parts of the application.

Next, we must manually include any new components in the module they're located in. For now, we only have one root module. So, we'll include reference to TaskListComponent in the root module's app.module.ts file.

First, we need to import our component file at the top of the module's file:

app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }   from './app.component';
import { FormsModule }  from '@angular/forms';
import { TaskListComponent }  from './task-list.component';

...

Then we need to include it in the declarations array (under the @NgModule decorator):

app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }   from './app.component';
import { FormsModule }  from '@angular/forms';
import { TaskListComponent }  from './task-list.component';


@NgModule({
  imports: [ BrowserModule,
            FormsModule ],
  declarations: [ AppComponent,
                  TaskListComponent],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

Refactoring Code

Let's move code responsible for displaying our list of Tasks from our AppComponent root component into our new TaskListComponent.

First, we'll move the <ul> containing the list of Tasks from AppComponent into TaskListComponent:

app/task-list.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'task-list',
  template: `
  <ul>
    <li [class]="priorityColor(currentTask)" (click)="isDone(currentTask)" *ngFor="let currentTask of tasks">{{currentTask.description}} <button (click)="editTask(currentTask)">Edit!</button></li>
  </ul>
  `
})

export class TaskListComponent {

}

Now let's make sure to include the tasks array this component will be responsible for listing along with any methods the code above calls (except for the editTask() method, which will go inside EditTaskComponent).

app/task-list.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'task-list',
  template: `
  <ul>
  <li [class]="priorityColor(currentTask)" (click)="isDone(currentTask)" *ngFor="let currentTask of tasks">{{currentTask.description}} <button (click)="editTask(currentTask)">Edit!</button></li>
  </ul>
  `
})

export class TaskListComponent {
  tasks: Task[] = [
    new Task('Finish weekend Angular homework for Epicodus course', 3),
    new Task('Begin brainstorming possible JavaScript group projects', 2),
    new Task('Add README file to last few Angular repos on GitHub', 2)
  ];

  isDone(clickedTask: Task) {
    if(clickedTask.done === true) {
      alert("This task is done!");
    } else {
      alert("This task is not done. Better get to work!");
    }
  }

  priorityColor(currentTask){
    if (currentTask.priority === 3){
      return "bg-danger";
    } else if (currentTask.priority === 2) {
      return  "bg-warning";
    } else {
      return "bg-info";
    }
  }
}

After these changes, our root component should look like this:

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

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1>To Do List for {{month}}/{{day}}/{{year}}</h1>
      <h3>{{currentFocus}}</h3>
      <hr>
      <div>
        <div *ngIf="selectedTask">
          <h3>{{selectedTask.description}}</h3>
          <p>Task Complete? {{selectedTask.done}}</p>
          <hr>
          <h3>Edit Task</h3>
          <label>Enter Task Description:</label>
          <input [(ngModel)]="selectedTask.description">
          <label>Enter Task Priority (1-3):</label><br>
          <input type="radio" [(ngModel)]="selectedTask.priority" [value]="1">1 (Low Priority)<br>
          <input type="radio" [(ngModel)]="selectedTask.priority" [value]="2">2 (Medium Priority)<br>
          <input type="radio" [(ngModel)]="selectedTask.priority" [value]="3">3 (High Priority)
          <button (click)="finishedEditing()">Done</button>
        </div>
      </div>
    </div>
  `
})

export class AppComponent {
  currentFocus: string = 'Angular Homework';
  currentTime = new Date();
  month: number = this.currentTime.getMonth() + 1;
  day: number = this.currentTime.getDate();
  year: number = this.currentTime.getFullYear();
  selectedTask = null;

  editTask(clickedTask) {
    this.selectedTask = clickedTask;
  }

  finishedEditing() {
    this.selectedTask = null;
  }

export class Task {
  public done: boolean = false;
  constructor(public description: string, public priority: number) { }
}

Now we'll instruct Angular where to render TaskListComponent by placing its <task-list></task-list> tags in our root component, right where code for listing Tasks used to reside:

app/app.component.ts
...
@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1>To Do List for {{month}}/{{day}}/{{year}}</h1>
      <h3>{{currentFocus}}</h3>

      <task-list></task-list>

     ...
...

Accessing the Model from Multiple Components

If you're using Atom's TypeScript package, you'll notice we have an error in task-list.component.ts. (If you're not using the apm package, you'll receive an error in the console, but its message may slightly differ from the one below).

typescript-error-in-atom

The TaskListComponent doesn't have access to our model so it doesn't know what Task is.

We could copy our Task model class into TaskListComponent, but our root component also needs it so we'd have to include it in both files. That's not very DRY.

Instead, whenever multiple files require access to the same model, create a separate file for the model and import it wherever the class is required.

We'll create a new file in the app directory called task.model.ts. Model file names should be lowercase, and include the name of the class they contain followed by a .model.ts extension.

Then, we'll move our Task model from the root component (app.component.ts) into our new dedicated model file (task.model.ts):

app/task.model.ts
export class Task {
  public done: boolean = false;
  constructor(public description: string, public priority: number) { }
}

Next we'll add an import statement at the the top of all files that require the Task model (app.component.ts, and task-list.component.ts).

app/app.component.ts
import { Component } from '@angular/core';
import { Task } from './task.model';
...
app/task-list.component.ts
import { Component } from '@angular/core';
import { Task } from './task.model';
...

Now we can declare our model class in one place and allow multiple components to use it while still keeping our code DRY.

If we launch our application, we should see our list of tasks in the browser.

However, if you attempt to click the "Edit" button next to a Task, you'll receive errors:

edit-task-errors

Don't worry. This is expected. The TaskListComponent still contains an event binding to call editTask(). But we have not moved editTask() from the root component into TaskListComponent, because we will be moving it to a dedicated EditTaskComponent instead. We'll walk through these additional changes in the next few lessons.


Example GitHub Repo for Angular 2 To Do List