Creating Reusable Component with Angular

Introduction

Since the last tutorial, I have had difficulty coming up with a new one. It is funny, after so many years of writing tutorials, I am finally getting to a point where there is not much to write about -- writer's block. This is not going to stop me. But I had to think very hard, to find something that is worthy of discussion.

One concept I haven't discussed in my tutorials is creating a reusable component in an Angular application. So I decided this is the concept I will discuss in this tutorial. The problem is that I have no idea what the sample application looks like, writer's block. I started looking into the components in the Bootstrap framework in the hope of finding some inspiration. Bootstrap framework always included a useful component called the "Progress Bar". As far as I know, this component has always been read-only. That is, it displays a progress/status (such as a percentage value) and users cannot interact with it. When I realized this fact, I thought to myself why is this component always read-only? Can I do something about it? It turned out that there was a way to make this component interactive. And it worked gloriously when it did work!

In this tutorial, you will see that I had multiple problems to solve. I have to learn how reusable components work; how the child component communicates with the parent component, and how I can turn a static component into something that is interactable with users. All these will be answered in this tutorial.

Overall Architecture

The sample application of this tutorial is a color picker. It uses three progress bars from the Bootstrap framework to represent the values of red, green, and blue. Users can click anywhere in the progress bar to set the new value for the colors. There are also three input fields that display the current values of the primary colors red, green, and blue. Users can also change these values directly using these input fields. The changes will also reflect on the affected progress bars. Lastly, a rectangle at the bottom displays the color represented by the values of the three primary colors.

Here is a screenshot of the application:

This application has only the Angular code and no back-end services. I intend to focus on how to create reusable components and how to make the progress bar interactive. The three progress bars are the same reusable component. Designing it as a reusable component is a good idea. I will show you why that is. In the next section, I will start on creating the interactive progress bar, then how to create a reusable component from that, and why making the design reusable is a good idea.

Making an Interactive Progress Bar

For this tutorial, the biggest problem I must solve is making the progress bar interactive. It is designed to display the progress. As far as my research goes, nowhere describes it as interactable. When I realized this, I wondered why no one had tried to make this cool component as an interactive component. I am sure that someone has tried to make this an interactive component. They probably never documented it. If you wonder how to make this component interactive, this section is something you want to read carefully. If my explanation does not make sense, no problem. Please download the sample application and dissect it.

This is a screenshot of the progress bar from Bootstrap:

The equivalent HTML mark up of the above screenshot looks like this:

<div class="progress">
  <div class="progress-bar" role="progressbar" style="width: 75%" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
</div>

As you can see from the above screenshot, the progress bar component is made of two different pieces of <div>. The outer one with the class "progress" is the gray bar representing the overall progress. The inner one displays the actual progress value. It was the bluish color stripe that did not go all the way covering over the gray bar.

I imagined the interaction with such a component would be like when a user clicks on a place on the progress bar, the progress would be set at that place, or in close proximity to that place. The problem is how I can find the relative distance of the mouse cursor position to the beginning of this progress bar, and the total length of the bar. Once I obtain these two values, I can calculate the percentage of the progress bar. After some research and detective work, I realized that getting these two values is not hard.

Turned out, that every element on a page has a relative position and dimensions based on the relative position. We can obtain this information freely if we know how to look for it. If we can get a reference of the element, we can get the width of it from the referenced object's offsetWidth property. It is easy, isn't it? The next question is, how do I determine the position of the mouse cursor within a given element? By following the same train of thought, it turned out that getting the mouse cursor relative to the element can be done by capturing the mouse click event, and then from the event we can find the position. The properties offsetX and offsetY of the event are the coordinates of the cursor relative to the element where the cursor is.

Now that we know how to calculate the percentage of the progress based on the cursor position and length of the whole progress bar, we can put all these into a function/method, like this:

public handleClickProgressBar($event: any) {
   let progressBarWidth: number = 0;
   let cursorXPos: number = 0;

   if ($event) {
      cursorXPos = $event.offsetX;
      if ($event.currentTarget) {            
         progressBarWidth = $event.currentTarget.offsetWidth;

         this.barWidth = cursorXPos / progressBarWidth;
         this.barWidth = Math.ceil(this.barWidth * 100);
         this.barUIWidth = "" + this.barWidth + "%";

         console.log(this.barWidth);

         this.updateProgress.emit({
            percentageVal: this.barWidth
         });
      }
   }
}

The code logic in the above method isn't complex. What is complex is the process of finding the solution, which I won't discuss in this section. It is a waste of bytes and has no good purpose for this tutorial. The method takes in a parameter and I call it $event. It has the anonymous type of any. This parameter is the reference of the mouse click event. The work begins within the if block. First, we need to get the x-coordinate of the cursor position when the mouse click happens. It is done with the following line:

...
   cursorXPos = $event.offsetX;
...

This line will get the x-coordinate of the cursor position. This position is relative to the left-top point of the element on the page. Next, we need to get the length of the progress bar. Using the values of the x-coordinate and the length of the bar, we should be able to determine the new position where the progress should be displayed on the progress bar.

To figure out the lengtgh of progress, all we need to do is find the element that received the mouse click event and invoked this method to handle it. A little more detective work reveals it is quite simple:

...
   progressBarWidth = $event.currentTarget.offsetWidth;
...

The reference of $event.currentTarget is the element that received the mouse click event and invoked the above method to handle the event. The relative width of this element is stored in the property called offsetWidth. Getting these two values is easy because modern browsers do all the heavy lifting.

Before I explain the calculation of the new progress value for display, I need to explain the configuration of the progress bar. The bar is configured to display the progress value from 0 to 100 (yes, percentage). Also, as I have mentioned before, the progress bar component has two parts: the background displays the shade of gray, and the top displays the colorful progress. Both parts are made up via the tag <div>. The width of the top part can be specified by a percentage value (via the style attribute).

Now that we know how the progress bar is configured, we can recalculate the length of the progress, then display it. This is how it is done:

...
   this.barWidth = cursorXPos / progressBarWidth;
   this.barWidth = Math.ceil(this.barWidth * 100);
   this.barUIWidth = "" + this.barWidth + "%";
...

The above code segments are all needed to complete the mouse click event handling for the reusable component. It is time to show you where to add the event-handling method to the component. This is the HTML markup of the reusable component:

<div class="progress"
     (click)="handleClickProgressBar($event)">
   <div class="progress-bar"
        [ngClass]="barColor"
        role="progressbar"
        [style.width]="barUIWidth"
        [attr.aria-valuenow]="barWidth"
        aria-valuemin="0"
        aria-valuemax="100">{{barTitle}}</div>
</div>

I have highlighted the two important parts of the above markup codes with bold font. The first bold line is attaching the event handler method to the element. The element displays the entire range of the progress bar. I attach the event handling method to this element so that the method can easily get the entire length of the progress bar. The alternative would be attaching the event handling method to the inner <div>. The problem with this approach is that this inner <div> is used to display the current progress. The length of this inner <div> will not be the entire bar if the value is less than 100%. I have to go to the parent element (the outer <div>) to find the actual length of the entire bar. This is the reason I chose outer <div> for the event handler method. Why choose a long way when there is a shortcut you can use?

The second part in bold font is to set the width value of the inner <div>. The width is calculated in the last TypeScript code segment I have shown, and this is where it is assigned. The syntax is special. It specifies that for the HTML element attribute style's width property, bind the component object property barUIWidth to it.

When you get the sample application running, try clicking the progress bars athe random poistions and see the progress being set at that location (or to the close proximity of the position). In the next section, I will discuss how we can package the work I have done in this section into a reusable compoment.

Designing a Reusable Component

Creating a reusable component is a lot easier than designing the interactive progress bar. There are plenty of tutorials on the net that explain how. Creating a reusable piece in the latest Angular framework is more accessible than doing the same in AngularJS. The communication between the parent component and the child component is easier to understand as well. Based on what I learned in the 30 minutes of research I have done, all we needed was to understand the annotation of @Input() and the annotation @Output, as well as the concept of using a property setter as a watcher. Once I understood these three concepts, designing a reusable component was easy to do. There are other concepts we must know to implement so that the reusable component can work properly. We will go over the sample code and I will point out all the nitty bits that one should learn.

I made the progress bar work on the index page. Then I copied the HTML markup and the TypeScript code into separate files, making them an independent component. The HTML markup code I listed in the previous section is located in a file called "progressSlider.component.html". In the same folder, you will find the source code file "progressSlider.component.ts". This file defines the behaviors of the component.

This is the TypeScript code for the progress bar:

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

@Component({
   selector: 'progress-slider',
   templateUrl: './progressSlider.component.html'
})
export class ProgressSliderComponent implements OnInit {
   @Input() barColor: string;
   private _barWidth: number;
   @Input() barTitle: string
   public barUIWidth: string;
   @Output() updateProgress = new EventEmitter();

   constructor() {
   }

   public get barWidth(): number {
      return this._barWidth;
   }

   @Input()
   public set barWidth(val: number) {
      this._barWidth = val;
      this.barUIWidth = "" + this.barWidth + "%";
   }

   ngOnInit() {
   }

   public handleClickProgressBar($event: any) {
      let progressBarWidth: number = 0;
      let cursorXPos: number = 0;

      if ($event) {
         cursorXPos = $event.offsetX;
         if ($event.currentTarget) {            
            progressBarWidth = $event.currentTarget.offsetWidth;

            this.barWidth = cursorXPos / progressBarWidth;
            this.barWidth = Math.ceil(this.barWidth * 100);
            this.barUIWidth = "" + this.barWidth + "%";

            console.log(this.barWidth);

            this.updateProgress.emit({
               percentageVal: this.barWidth
            });
         }
      }
   }
}

The above source code contains the event handler method which already discussed in the previous section. The only part which I didn't explain is the last part of the method. I will get to it in a bit. Let's first look at the code that I have not explained. First, it is the declaration of the component:

...
@Component({
   selector: 'progress-slider',
   templateUrl: './progressSlider.component.html'
})
export class ProgressSliderComponent implements OnInit {
...

The above code segment is fairly easy to understand. It creates a component class and binds this class to any element of the name <progress-slider>. The templateUrl specifies where the HTML markup that will replace this element. The HTML markup used for the replacement, you guessed, is the last code piece I have listed in the last section.

Next, I will show you the properties declaration, the properties' getter and setters, like this:

...
   @Input() barColor: string;
   private _barWidth: number;
   @Input() barTitle: string
   public barUIWidth: string;
   @Output() updateProgress = new EventEmitter();

   constructor() {
   }

   public get barWidth(): number {
      return this._barWidth;
   }

   @Input()
   public set barWidth(val: number) {
      this._barWidth = val;
      this.barUIWidth = "" + this.barWidth + "%";
   }
...

In this part of the source code, I have decorated the properties with @Input(). By decorating the properties with this, the property can be used as an attribute of the element <progress-slider>. These input attributes will be able to bind the properties of the parent component to this child component. In addition, whenever the property in the parent component changes value, the change will be propagated into this child component. This is how the parent component communicates with the child component -- by value propagation. If you remember I said earlier that there is no need for a watcher (something useful in AngularJS). That is because we have property accessors (getters and setters). In this class, I have defined a setter for _barWidth. This setter is decorated with @Input(). It will be able to bind with the properties of the parent component. When the value of the property from the parent component changes, this setter will be called. In it, we can set the private property behind this setter, and change the value of another property. This is what setter is used for, almost equivalent to a watcher in AngularJS.

There is also a property decorated with annotation @Output(). This property has the type of EventEmitter. Any property that has the annotation of @Output() must have the type EventEmitter. This property can also be an attribute of the element <progress-slider>. It can bind to a callback method in the parent component. You can see this property being used by the event handler method, like this:

public handleClickProgressBar($event: any) {
   ...

   if ($event) {
      ...
      if ($event.currentTarget) {            
         ...

         this.updateProgress.emit({
            percentageVal: this.barWidth
         });
      }
   }
}

As you can see, the output property can be used to invoke .emit(<obj to be passed back>). What happens is that the callback method bound to this property would be invoked and process the object that was passed back from here. In the event handler method, once the mouse click event is handled, the new length of the actual progress is updated, and we pass the new value (a percentage value between 0 and 100) to the parent component.

All the secrets of this component atre laid bare in this section. It is fun, isn't it. I am amazed that it could be so simple to create a reusable component. In the next section, I will discuss how this reusable component is integrated into the index page.

Integrating Reusable Component

In this section, I will discuss how this reusable component is utilized. There is only one page in this application. On it, there are three interactive progress bars. All are the same reusable component. Each represents the value of a primary color (red, green, and blue). The progress bar uses a scale from 0 to 100. The value range for the primary color is from 0 to 255. So when I use the progress bar for interaction, there has to be some kind of value conversion. This is something all of us must be aware of. When we get to this, I will point it out. First, I like to show you how the component is added to the HTML page. In the file index.component.html, you will see this:

<div class="row mb-2">
   <div class="col">
      <progress-slider [barColor]="barColorRed" [barWidth]="barWidthRed" [barTitle]="barTitleRed" (updateProgress)="handleRedBarValuaUpdate($event)" />
   </div>
</div>
<div class="row mb-2">
   <div class="col">
      <progress-slider [barColor]="barColorBlue" [barWidth]="barWidthBlue" [barTitle]="barTitleBlue" (updateProgress)="handleBlueBarValuaUpdate($event)" />
   </div>
</div>
<div class="row mb-2">
   <div class="col">
      <progress-slider [barColor]="barColorGreen" [barWidth]="barWidthGreen" [barTitle]="barTitleGreen" (updateProgress)="handleGreenBarValuaUpdate($event)" />
   </div>
</div>

In this markup code segment, I have highlighted three places where the reusable component is used. The element on the page is called <progress-slider>. For this element, there are four attributes:

  • barColor: This is the input property of the reusable component. It binds to a property called barColorRed (for the first one) of the current component. Not only the value referenced by barColorRed is passed to the reusable component, but the change of the value referenced by barColorRed will also be reflected in the reusable component.
  • barWidth, barWidth, barTitle: They all behave the same as barColor.
  • updateProgress: This one binds the callback method of the current component to the reusable component. When the reusable component decides to contact the parent component, it will use this callback method to make the contact.

Do these names look familiar? They should be. These are the component properties that are decorated with @Input() or @Output() annotations in the ProgressSliderComponent component. This is how the properties bindings work between components.

In the index.component.ts file, there are three callback methods. All of them do the same work. We will examine one -- handleRedBarValuaUpdate():

public handleRedBarValuaUpdate(evt: any): void {
   // XXX, in case you wonder, this._maxColorVal = 255
   console.log(evt);
   if (evt) {
      this.redColorVal = Math.ceil(evt.percentageVal * this._maxColorVal / 100);
   }
}

In the last section, I have listed the source code with the component property updateProgress (the one that was decorated with @Output). It can be called with method emit(). What happens after this is invoked is that in the parent component, the bounded callback method will be invoked to handle the value passed by the invocation od emit() method in the child component. This sounds confusing, doesn't it? Let me just summarize:

  1. When the child component is rendered during paging loading, the initial value of the progress bar is set by the input attribute using values from the parent component.
  2. User interacts with the progress bar using the mouse cursor. The child component has a mouse click event handler. In it, the new progress value is calculated. The progress bar display will update.
  3. In the event handler, after the progress bar is updated. It will use the output property updateProgress (which has the EventEmitter type) to invoke the method emit(). This will pass the updated percentage value back to the parent component.
  4. The parent component has the callback method handleXXXBarValuaUpdate() (XXX is the color of the progress bar), which will be invoked and update the value of the target progress bar.

It looks super complicated. But it is not. All you need to do is run the sample application and set a couple of breakpoints at the places I described above. Afterward, you can click the progress bars and see how the execution flows run.

When the new value of the progress bar passes to the parent component, the color display at the bottom should be updated. It is done by the setters for the color values in the parent component. This is an example that updates the background color of the <div> of color display:

public set redColorVal(val: number) {
   this._redColorVal = val;
   this.barWidthRed = Math.ceil(this._redColorVal / this._maxColorVal * 100);
   this.colorToDisplay = "rgb(" + this._redColorVal + ", " + this._greenColorVal + ", " + this._blueColorVal + ")"
   console.log(this.colorToDisplay);
}

In the sample application, I also added three text boxes for users to set the color value by hand. The value can be from 0 to 255. Every time the user inputs a new value in the text box. The above setter associated with the property that binds to the text box would be called. You can try this out yourself. In the text box for "Red", and see the progress bar for the color updates, and the color display at the bottom also changes accordingly.

This is all there is about this sample applciation. In the next section, I will explain how to run this sample application. It is much easier than what I have done in my previous tutorials.

How to Run the Sample Aplication

I have good news for you -- running this ample application is super easy. There are a couple of steps involved. First, please download the sample application and unzip it to a location. After that, run the following command to install all the needed node.js libraries:

npm install

After the libraries are installed successfully, you can run the following command to start the web application on a local web server:

ng serve

Once the web server is started successfully, it is time to test the web application. Please use a browser and navigate to:

http://localhost:4200/

The web application would look like this:

Try to click on the progress bars, any of them, and see if the color value in the corresponding text box changes and the combined color display at the bottom. Alternatively, you can change the primary color values in the text boxes. The progress bar will stretch or shrink as the value changes. These show that the two-way communications between parent and child components are working. You should use the debug breakpoints and get a better understanding of how the mechanisms work.

Summary

Well, this is a fun tutorial. When I started, I was stuck, not sure what to write in this tutorial. After some research, I was able to come up with this. During the development process, I have learned a lot. The first one I learned is how to turn a static Bootstrap component into something that is interactive. On that basis, I was able to design a reusable component from this. This is a double dragon for both you the reader and me.

This is the fiftieth tutorial I have written. I am again thrilled with how this has turned out. The tutorial itself is not as high quality as I had hoped. The sample application is top quality. In the sample application, you would see how the progress bar from Bootstrap can be enhanced into an interactive component. I even showed you how to find the mouse cursor position and the length of the progress bar. From those, calculating the percentage value for the progress bar display would be easy. Creating the reusable component is not hard. All thanks to the fact that I learned how to create reusable components. This enabled me to learn the new concept quickly. As I have said many times, almost every new knowledge can always be built upon the old one. This is the best way to learn.

This has been a fantastic journey, getting this tutorial completed. I hope it can help you in some ways. If not, at least it is something that looks cool. Anyway, good luck with learning!

History

9/20/2023 - Initial Draft

Your Comment


Required
Required
Required

All Related Comments

Loading, please wait...
{{cmntForm.errorMsg}}
{{cmnt.guestName}} commented on {{cmnt.createDate}}.

There is no comments to this post/article.