Dynamically import module in Angular

Reading Time: 5 minutes

 335 total views

Introduction

The elements of Spanish menu application (https://github.com/railsstudent/ng-spanish-menu) are primarily texts and buttons, and the user interface looks plain on first glance. I want to make it interesting by rendering an icon when quantity is below threshold.

This is the final output:

The exclamation icon is loaded from angular-fontawesome followed by message, “Low Supply”.

I worked on the implementation twice:

Initially, static import FontAwesomeModule to the application, and used ng-if to conditionally render the icon and text. The solution had little code changes but the drawback was an additional of 32 kilobytes to the bundle size. The margin of increase is a lot considering the application is small and I am using only one icon of the library.

As the result of this discovery, the final design dynamically creates FaIconComponent and inserts it to an instance of ViewContainerRef. Then, inject Renderer2 and append “Low Supply” child to div parent.

This post will explain how I made the enhancement with the naive approach, what I discovered and the benefits of creating dynamic components in Angular.

Install Angular Fontawesome in Angular

Firstly, we have to install angular-fontawesome schematics into the Angular application.

ng add @fortawesome/angular-fontawesome@0.9.0

Add font-awesome icon statically

Secondly, import FontAwesomeModule in food-choice module such that all icons are available to render in template.

food-choice.module.ts

import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'

import { FoodChoiceFormModule } from '../food-choice-form'
import { FoodChoiceComponent } from './food-choice.component'

@NgModule({
  declarations: [FoodChoiceComponent],
  imports: [CommonModule, FoodChoiceFormModule, FontAwesomeModule],
  exports: [FoodChoiceComponent],
})
export class FoodChoiceModule {}

Thirdly, update component and template to display the icon and text conditionally.

// environment.ts
export const environment = {
  production: false,
  baseUrl: '/.netlify/functions',
  lowSupplyPercentage: 0.4,
}
// food-choice.component.ts

public ngOnInit(): void {
    this.remained = this.qtyMap ? this.qtyMap[this.choice.id] || 0 : 0
    this.minimumSupply = Math.ceil(this.remained * environment.lowSupplyPercentage)
}
// file-choice.component.html

<div class="flex items-center" *ngIf="remained > 0 && remained <= minimumSupply">
   <fa-icon [icon]="faExclamationTriangle" class="text-red-500 text-[1.35rem] mr-2"></fa-icon>
    <span class="text-red-500 text-xl">Low supply</span>
</div>

Lastly, I examine the impacts of angular-fontawesome on the bundle size. The bundle size should increase but the degree of decrease is my major focus.

Install source-map-explorer to analyze the bundle of the project

npm i --save-dev source-map-explorer

Build the project and enable source-map flag

ng build --source-map=true

Finally, analyze the source map to gather information on the size of different packages.

./node_modules/.bin/source-map-explorer ./dist/ng-spanish-menu/main.<hash sum>.js

The bottom right displays the size of angular-fontawesome and it is roughly the same size as rxjs. I have to improve the bundle size because one icon leads to a slightly bloated main.js.

Create dynamic fontawesome icon and text

This approach requires more steps than its counterpart but the bundle size shrinks eventually and the benefits outweigh the extra efforts.

Firstly, add a template reference (#lowSupplyRef) to the div parent. I will use the reference to append the “Low Supply” text later on.

// font-choice.template.html
<div class="flex items-center grow" #lowSupplyRef></div>

Secondly, define a viewContainerRef inside the div element to host instances of font-awesome icon.

// font-choice.template.html
<div class="flex items-center grow" #lowSupplyRef>
   <ng-container #viewContainerRef></ng-container>
</div>

Inside the component, declare a componentRef variable to hold a reference to font-awesome icon.

// food-choice.component.ts

public componentRef: ComponentRef<unknown> | null = null

Use @ViewChild() decorator to obtain viewContainerRef and lowSupplierRef.

// food-choice.component.ts

@ViewChild('viewContainerRef', { read: ViewContainerRef, static: true })
public viewContainerRef: ViewContainerRef

@ViewChild('lowSupplyRef', { read: ElementRef, static: true })
public lowSupplierRef: ElementRef

Next, define a function to create a dynamic font-awesome icon and insert it to viewContainerRef.

private async displayLowSupplyIcon() {
    const faExclamationTriangle = (await import('@fortawesome/free-solid-svg-icons')).faExclamationTriangle
    const FaIconComponent = (await import('@fortawesome/angular-fontawesome')).FaIconComponent
    const resolvedFaIconComponent = this.componentFactoryResolver.resolveComponentFactory(FaIconComponent)
    const faIconComponentRef = this.viewContainerRef.createComponent(resolvedFaIconComponent)
    faIconComponentRef.instance.icon = faExclamationTriangle
    faIconComponentRef.instance.classes = ['text-red-500', 'text-[1.35rem]', 'mr-2']
    faIconComponentRef.instance.render()
    this.componentRef = faIconComponentRef
}

The first import() statement imports the exclamation icon.

const faExclamationTriangle = (await import('@fortawesome/free-solid-svg-icons')).faExclamationTriangle

The next two lines of code create a FaIconComponent component.

const FaIconComponent = (await import('@fortawesome/angular-fontawesome')).FaIconComponent
const resolvedFaIconComponent = this.factoryResolver.resolveComponentFactory(FaIconComponent)

Then, we create an instance of ComponentRef<FaIconComponent>, assign the icon, specify tailwind CSS classes and render the svg.

const faIconComponentRef = this.viewContainerRef.createComponent(resolvedFaIconComponent)
faIconComponentRef.instance.icon = faExclamationTriangle
faIconComponentRef.instance.classes = ['text-red-500', 'text-[1.35rem]', 'mr-2']
faIconComponentRef.instance.render()
this.componentRef = faIconComponentRef

Next, define another function to append the “Low Supply” text to lowSupplierRef.

private renderLowSupplyText() {
    const lowSupplySpanElement = this.renderer.createElement('span')
    lowSupplySpanElement.classList.add('text-red-500', 'text-xl')
    lowSupplySpanElement.innerText = 'Low Supply'
    this.renderer.appendChild(this.lowSupplierRef.nativeElement, lowSupplySpanElement)
}

When quantity is low and icon has not rendered, render both icon and the text, and trigger change detection.

private async displayLowSupplyComponent() {
  if (!this.componentRef) {
     await this.displayLowSupplyIcon()

     this.renderLowSupplyText()
     this.cdr.detectChanges()
  }
}

When quantity reaches zero, destroys the components and clears viewContainerRef to prevent memory leak.

private destroyComponents() {
    if (this.componentRef) {
      this.componentRef.destroy()
    }

    if (this.viewContainerRef) {
      this.viewContainerRef.clear()
    }

    Array.from(this.lowSupplierRef.nativeElement.children).forEach((child) => {
      this.renderer.removeChild(this.lowSupplierRef.nativeElement, child)
    })
}

private async handleLowSupply() {
    if (this.remained <= 0) {
      this.destroyComponents()
    } else if (this.remained > 0 && this.remained <= this.minimumSupply) {
      await this.displayLowSupplyComponent()
    }
}

Finally, we call handleLowSupply() in ngOnInit and ngOnChanges.

public async ngOnInit(): Promise<void> {
    this.remained = this.qtyMap ? this.qtyMap[this.choice.id] || 0 : 0
    this.minimumSupply = Math.ceil(this.remained * environment.lowSupplyPercentage)

    await this.handleLowSupply()
}

public async ngOnChanges(changes: SimpleChanges): Promise<void> {
    ... omitted ...

    await this.handleLowSupply()
}

Study the bundle size

We change many codes and keep the same user interface. Did the efforts significantly reduce the bundle size?

Re-run the commands below

ng build --source-map=true
./node_modules/.bin/source-map-explorer ./dist/ng-spanish-menu/main.<hash sum>.js

The bundle size increases by 3 kilobytes and angular-fontawesome library is removed from the source map.

Dynamic import does not add angular-fontawesome to main.js and instead, it splits into a couple of lazy chunk files (457.5da21ff230e58ed7c939.js and 859.106542046a8d67d7e411.js).

Final thought

Static import third-party library increases the bundle size of Angular application and importing a large library can contribute to a big bundle. In this example, the naive approach led to a 10% increase of the bundle size.

Thanks to dynamic import, ComponentFactoryResolver and ViewComponentRef classes, I can load icon on the fly, achieve the same result yet the bundle size increases by a few kilobytes.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other web technologies.

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. ComponentFactoryResolver: https://angular.io/api/core/ComponentFactoryResolver
  3. ViewContainerRef: https://angular.io/api/core/ViewContainerRef
  4. Renderer2: https://angular.io/api/core/Renderer2

Tailwind CSS in JIT mode with Angular

Reading Time: 3 minutes

 168 total views

Introduction

Tailwind CSS (https://tailwindcss.com/) is a utility-first CSS framework with out-of-the-box classes for UI components.

For example, <p class="pl-2">hello world</> is equivalent to <p style="padding-left: 0.5rem;">hello world</h> and we achieve the effect without writing inline-style or custom class.

However, our components may require one-off style that Tailwind does not support and we cannot justify to include it in theme configuration.

Fortunately, Tailwind enables Just-In-Time (JIT) mode that generates the styles on demand. This feature allows dynamic style such as “w-[200px]” to fix the width of a card to 200px.

This post will show you the installation of Tailwind CSS, enable Just-In-time mode and add dynamic styles to style a div element.

Install Tailwind CSS in Angular

Firstly, we have to install the dependency of tailwind and tailwind plugins into the Angular application.

npm install --save-dev tailwindcss
npm install --save-dev @tailwindcss/forms @tailwindcss/typography
npx tailwindcss init

The npx command generates tailwind.config.js with default values

Enable Just-In-Time mode

Secondly, we have to enable JIT mode of tailwind; therefore, mode: 'jit‘ is added to tailwind.config.js.

module.exports = {
  mode: 'jit',
  content: ["./src/**/*.{html,ts}"],
  darkMode: 'media', // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
  ],
}

Then, we add file path in content array to purge all html and typescript files to maintain small bundle size in production. The last step of configuration is to import typography and forms plugins by require('@tailwindcss/typography') and require('@tailwindcss/forms')

Add Tailwind Base Styles to Angular

It is important to put tailwind directives at the beginning of the global stylesheet, style.scss. These are base styles that are accessible to all UI components of the application.

@tailwind base;
@tailwind components;
@tailwind utilities;

Finally, we are ready to generate on-demand style in our component.

Generate on-demand styles in Angular Component

Our use case is to set the width of food-card component to 300 pixels wide. The html template of food-card component looks like the following:

<div style="display: flex; flex-direction: column; margin-right: 0.5rem; border: 2px solid black; width: 300px;">
      <label name="name" class="item card-row">
        <span class="field">Name:</span>
        <span class="field-text">{{ ordered.name }}</span>
      </label>
      <label class="item card-row" name="description">
        <span class="field">Description:</span>
        <span class="field-text">{{ ordered.description }}</span>
      </label>
</div>

Except width: 300px;, we can substitute the rest with tailwind utility classes.

I don’t believe the width style should be part of the configuration because it is used one time. The just-in-time mode provides the solution we need; w-[300px] generates custom class at run time and changes the width to 300 pixels wide.

After the modification;

<div class="flex flex-col mr-2 border-solid border-2 border-black w-[300px]">
      <label name="name" class="item card-row">
        <span class="field">Name:</span>
        <span class="field-text">{{ ordered.name }}</span>
      </label>
      <label class="item card-row" name="description">
        <span class="field">Description:</span>
        <span class="field-text">{{ ordered.description }}</span>
      </label>
</div>

Verify the result

The final stage is to verify the custom style actually works and the div component has the expected width.

We can open Chrome inspector, hover on the <div> element and inspect its width property. As the image indicates, we have successfully fix the width to 300 pixels.

Final thought

I really like to use Tailwind in Angular because it saves the efforts of authoring custom css classes in SCSS files. After switching to tailwind, I got rid of most of the SCSS files and replaced the styles with the counterpart utility classes. When utility class is unavailable, it is convenient to generate the custom class on the fly with arbitrary value. Authoring UI components can’t get any easier in web development.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other web technologies.

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. Tailwind: https://tailwindcss.com/docs/installation

Automate release management in Angular

Reading Time: 4 minutes

 118 total views

Introduction

Whether it is enterprise application or open source project, they will schedule for release to announce new features and bug fixes. The project will need new version and tag after the release to keep track of changes that occur between two releases.

When manager ask development team the deliverable of the current release; change log becomes handy because developers can present it to manager to review.

However, making a change log by hand is time consuming because developers have to go through git log and choose changes that have great values such as enhancements and bug fixes.

Developers are lazy and if they can find tool that can generate change log, they will use it without hesitation.

standard-version is an open source library that fulfills the requirement and you will love it if you have already written commit messages according to commitlint convention.

The effort to add standard-version is minimal and you can find all the steps in this blog post.

Install dev dependencies

npm i --save-dev standard-version

Add scripts to package.json that run to cut new release for an Angular application

// package.json
{
  "release": "standard-version -t '' -a",
  "release:patch:dryrun": "npm run release  -- --dry-run --release-as patch",
  "release:patch": "npm run release  -- --no-verify --release-as patch",
}

npm run release uses standard-version to cut new release, generate CHANGELOG.md and add new tag. The flag -a commits all generated changes while -t '' replaces the prefix of tag from v to blank string.

npm run release:patch:dryrun tests patch release in dry mode. When the script is successful, the simulation bumps the version of package.json and package-lock.json, outputs git commits to CHANGELOG.md and tag the new version to main branch.

Example of standard-version in dry run mode:

Configurations of of standard-version

standard-version supports lifecycle scripts and one of them is posttag that is executed after adding new tag. The posttag event is the ideal hook to push both commit and tag to repository automatically.

Moreover, the library has the option to show the type of commits in the change log. My preference is to output features and bug fixes to the file to highlight important changes in the new release.

We can find the configurations in .versionrc.json:

// .versionrc.json
{
  "types": [
    { "type": "feat", "section": "Features" },
    { "type": "fix", "section": "Bug fixes" }
  ],
  "scripts": {
    "posttag": "git push --follow-tags --force"
  }
}

Commit messages begin with feat: prefix belongs to Feature section whereas fix: prefix goes to Bug fixes section.

git push --follow-tags --force command force push commit and tag to my repository.

Cut a new release

The next feature of the project is adoption of Tailwind CSS and apply their CSS styles to beautify the components. The results look good in storybook; therefore, I decide to create a 0.0.7 release.

The release process is straightforward: npm run release:patch script automates the process from bump version, tagging to change log update.

The –no-verify flag ensures that husky hooks do not run in the commit step of the release and the release process is expected to complete quickly.

Change log accumulates features and bug fixes in release 0.0.7.

If I click any git commit id, I will see all the changes in the files.

One script replaces all the manual work of version management.

Final thought

In the past, developers are responsible for post-release activities such as version increment and adding version tag. Sometimes, developers forget because they are busy to implement new features and fix bugs. Nowadays, there are plenty of tools to facilitate release management and developers can issue a single script to automate the mentioned tasks. There is no excuse of not defining the standard procedure of release at work or in open source projects

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular, architecture and good engineering practice.

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. Standard version: https://github.com/conventional-changelog/standard-version