Angular - Working with components hierarchy
Tags: javascript, angular, webdev
Published: 2022-01-04 08:22:26

On Angular and other frontend frameworks or libraries like React or Next we work by creating components. This components allows us to:

- Separate responsibilities.
- Re-use code.
- Makes coding easier.
- Facilitates maintenance.

In order to achieve what I mentioned above we have to start thinking about some things before we start coding:

- How many components do I need?
- Which will be his responsibility?
- Can I reuse it? 

Based on components duties we can sort components into 2 groups:

- **`Smart components`**: Keep all functions and are responsible for getting all the information shown on `dumb components`. They are also called `application-level-components`, `container components` or `controllers`.

- **`Dumb components`**: Their only responsability is to show information or execute functions from the `smart component`. Also called `presentation components` or `pure components`.

Ok this is the theory but let’s see one example of smart and dumb components.

## Components hierarchy in action

To start I will create a new angular app:

```bash
ng new angular-hierarchy-components --style=scss --routing=true --skipTests=true
```

I will create a very basic app that is just a list and a form and buttons to add and remove elements to that list. At first I will do everything on the `app.component` to later refactor it using `smart` and `dumb` components.

This is all my code on the `app.component.ts` and `app.component.html`:

`app.component.ts`:

```javascript
export class AppComponent {
  title = 'angular-hierarchy-components';

  brands: string[] = [`Mercedes`, `Ferrari`, `Porsche`, `Volvo`, `Saab`];

  remove(id: number) {
    this.brands.splice(id, 1);
  }

  new(brand) {
    this.brands.push(brand.value);
  }
}
```

All I have is a brands list and 2 functions `remove` to remove brands from the list and `new` to add new brands to the list.

And this is the `app.component.html`:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="container">
      <div class="container__form">
        <form #root="ngForm" (ngSubmit)="new(newBrand)">
          <input type="text" name="brand" #newBrand />
          <button type="submit" #sendButton>Add</button>
        </form>
      </div>
      <div class="container__brand" *ngFor="let brand of brands; let i = index">
        <div class="container__brand__name">
          {{ brand }}
        </div>
        <div class="container__brand__button">
          <input type="button" (click)="remove(i)" value="x" />
        </div>
      </div>
    </div>
  </body>
</html>
```

I have a form that when on submit runs the `new` function that adds a new brand to the brands list and a `ngFor` that prints each brand name and a button to execute the `remove` function that removes the brand from the list.

![angular hierarchy components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ku08hdrszafe46gykn9y.gif)

This code works perfectly but I see some weakness on init:

- There’s no way to reuse the code that prints out the brand list and the button to remove the brands name. If I want to implement this functionality on the same app but for clothing brands I will have to repeat the code.

- If the app keeps growing I will have to stack all the functionalities on the `app.component.ts` so after adding each functionality the app turns out to be more and more difficult to maintain.

To solve he points I mentioned above I will split my code on `smart` and `dumb` components.

I will start by creating the `smart component` that will contain:

- The brands list.
- The `new` method to add new brands to the list.
- The `remove` method that removes brands from the list.

## Splitting my code to smart and dumb components

### Creating the smart component
To solve he points I mentioned above I will split my code on `smart` and `dumb` components.

I will start by creating the `smart component` that will contain:
- The brands list.
- The `new` method to add new brands to the list.
- The `remove` method that removes brands from the list.

On the terminal I create the smart component as a regular one:

```bash
ng generate component smartComponent
```

Usually I create the `smart components` to use as pages so I name it like `blogPage` or something like that but for this case I will just call it `smartComponent`.

On this component I will move the code I had on my `app.component.ts` to `smart-component.ts` so now it will look like this:

```javascript
export class SmartComponentComponent implements OnInit {
  constructor() {}

  ngOnInit(): void {}

  brands: string[] = [`Mercedes`, `Ferrari`, `Porsche`, `Volvo`, `Saab`];

  remove(id: number) {
    this.brands.splice(id, 1);
  }

  new(brand: string) {
    this.brands.push(brand);
  }
}
```

Nothing new yet.

Now I will have to remove the default content on the `smart-component.component.html` and set the layout to render the `dumb components` and I will have to create two `dumb components`:

- One component for the form to add new brands.
- Another to render the brands name and the remove button.

This is the layout:

```html
<div class="container">
  <div class="container__form">
   <!-- here goes the brands form -->
  </div>
  <div class="container__brand" *ngFor="let brand of brands; let i = index">
   <!-- Here goes the brands name component -->
  </div>
</div>
```

### Creating the dumb components

#### Creating the list-element component

Now let’s go to the `dumb components`.

First I will create the `list-element` components. This component will render one brand’s name and a button close to it to remove the brand from the list.

I create the component as a regular one:

```bash
ng generate component listElement
```

Now on the `list-element.component.ts` I have to define:
- The brand 's name.
- The brand’s id (actually the position on the brands name array).

But wait, we did not agree that the brands array and all the information was on the `smart component`? Yes. The `smart component` will hold all the information and functions but will pass the brand’s name and array position to the `dumb component` in our case `list-element` using `angular` `input binding`.

To do that we first have to import `Input` from `@angular/core` on the `list-element.component.ts` component:

```javascript
import { Component, Input, OnInit } from '@angular/core';
```

Now we can use the `@Import()` decorator to define the values we are expecting:

```javascript
@Input() brand: string;
@Input() id: number;
```

This way we are telling our component that he’s going to receive the brand's name and id (actually array position on the smart component).

Now let’s render the name and a button on the `list-element.component.ts`:

```html
<div class="container__brand">
  <div class="container__brand__name">
    {{ brand }}
  </div>
  <div class="container__brand__button">
    <input type="button" value="x" />
  </div>
</div>
```

This way we can render the name and a button on the screen.

Now on this same component we have to implement a method that allows us to execute the remove method we have on the `smart component`.

To execute the `remove` function we defined on the `smart component` from the `list-element component` we have to use another functionality from `angular` called `Output` in conjunction with `EventEmitter`. This will allow us to “emit” events to the `smart component` in order to execute methods.

First let’s add the `Output` and `EventEmitter` to our import:

```javascript
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
```

Now I can use the `@Output` decorator and the `EventEmitter`:

```javascript
@Output() removeEvent = new EventEmitter<number>();
```

And in my `list-element.component.ts` I will define a method that will trigger the `EventEmitter` when the user clicks on the remove button:

```javascript
removeBrand(id: number) {
    this.removeEvent.emit(id);
}
```

This method will receive the array position of the brand and emit it to the `smart component` so the `remove` method on the `smart component`is executed and the brand is removed from the list.

Now on the `element-list.component.html` we have to implement this method when the user clicks the remove button:

```html
<div class="container__brand">
  <div class="container__brand__name">
    {{ brand }}
  </div>
  <div class="container__brand__button">
    <input type="button" (click)="removeBrand(id)" value="x" />
  </div>
</div>
```

Ok, now let's connect the `smart component` with the `element-list component`. The `smart component`will be responsible to loop the brands list and use the `list-element`component to render the brands name and a button to remove. On the `smart-component.html` we will use the `element-list` component and pass to it the brands name and array position:

```html
<div class="container">
  <div class="container__form">
   <!-- here goes the new brand form component -->
  </div>
  <div class="container__brand" *ngFor="let brand of brands; let i = index">
    <app-list-element
      [brand]="brand"
      [id]="i"
      (removeEvent)="remove($event)"
    ></app-list-element>
  </div>
</div>
```

Let’s take a look at the `app-list-element` component tag. We can see we are using 3 parameters/attributes:

- brand: it’s the brand’s name.
- id: the array position for the brand.
- (removeEvent): it’s the removing brand event.

`brand` and `id` uses `[]` and events uses `()` it’s the same we do in Angular when we use `data-binding` or any other event like `click`:

- For binding data between components: [data].
- For binding events: (event).

Ok we’re done with this now let’s go for the new brands form.

#### Creating the new brand component

First we create the new brand form component:

```bash
ng generate component newBrand
```

This component will only contain the new brand form and `emit` the new brand’s name to the `smart component` so I will start by importing `Output` and `EventEmitter` to emit the new value:

```javascript
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
```

And define the new EventEmitter in the component using the `@Output` decorator:

```javascript
@Output() newEvent = new EventEmitter<string>();
```

And define a new method that will `emit` the new brand’s name to the `smart component`:

```javascript
new(brand: { value: string; }) {
    this.newEvent.emit(brand.value);
  }
```

And on the `new-brand.component.html` I add the form and set it to execute the `new` method when submit: 

```html
<form #newBrand="ngForm" (ngSubmit)="new(newBrandInput)">
    <input type="text" name="brand" #newBrandInput />
    <button type="submit" #sendButton>Add</button>
</form>
```

Now we only have to connect the `smart component` to the `new-brand component` on the `smart-component.component.html`:

```html
<div class="container">
  <div class="container__form">
    <app-new-brand (newEvent)="new($event)"></app-new-brand>
  </div>
  <div class="container__brand" *ngFor="let brand of brands; let i = index">
    <app-list-element
      [brand]="brand"
      [id]="i"
      (removeEvent)="remove($event)"
    ></app-list-element>
  </div>
</div>
```

On the `new-brand` tag component I have defined an event called `newEvent` and binded to the `new` method on `smart-component.component.ts`.

And that's all.

Here you can find a repository with 2 branches: [first one without components hierarchy](https://github.com/josecobo1/angular-hierarchy-components) and a [second one with the component hierarchy](https://github.com/josecobo1/angular-hierarchy-components/tree/components-hierarchy) I showed you on this post.