Conditional Templates for Angular Components

Written by Preston Lamb

Posted on Mar 22, 2021

angular

Getting Started with Deno

Learn how to start using Deno with my brand new course!

Buy the Course

tldr;

One of the many benefits of working in Nx workspaces is the ability to share code between applications. In an Angular application, that means being able to share components between multiple applications. The problem is that different applications may have different design systems, even though the functionality is the same. I recently ran into this issue, and wasn't sure the best route to go. In my situation, our old design system was based on (highly customized) Bootstrap. Our new app uses Tailwind. So I was a little stuck on what to do next. I first thought that I could just throw the Tailwind classes in the HTML mixed in with the Bootstrap classes. This would technically work, because if the app doesn't include Bootstrap styles, then it would only take the Tailwind styles and vice versa. But my worry was that the HTML templates would get out of control with extremely long class lists. But I realized there had to be a way to conditionally swap out the HTML for a template. That's what I decided to try and do, using the environment.ts file.

Component Outline

For this example, let's create a demo card component we can use. We'll make two HTML templates, one with some Bootstrap card styles and one with some Tailwind card styles.

Bootstrap:

<!-- card-bootstrap.component.html -->
<div class="card" style="width: 18rem;">
  <img [src]="imageUrl" class="card-img-top" alt="...">
  <div class="card-body">
    <h5 class="card-title">{{ cardTitle }}</h5>
    <p class="card-text">{{ cardDescription }}</p>
    <a [routerLink]="url" class="btn btn-primary">Go somewhere</a>
  </div>
</div>

Tailwind:

<!-- card-tailwind.component.html -->
<div class="p-20 bg-purple-100">
  <div class="bg-white rounded-lg shadow-lg w-full md:w-1/2">
    <img class="rounded-t-lg" [src]="imageUrl" alt="...">
    <div class="p-6">
      <h2 class="font-bold mb-2 text-2xl text-purple-800">{{ cardTitle }}</h2>
      <p class="text-purple-700 mb-2">{{ cardDescription }}</p>
      <a class="text-purple-600 underline text-sm hover:text-purple-500" [routerLink]="url">Go somewhere</a>
    </div>
  </div>
</div>

Now that we have the two templates, let's look at the component's TypeScript file.

// card.component.ts

@Component({
    selector: 'app-card',
    templateUrl: '',
    styleUrls: ['card.component.scss']
})
export class CardComponent {
    @Input() cardTitle: string;
    @Input() cardDescription: string;
    @Input() url: string;
    @Input() imageUrl: string;

    // other component logic
}

Now in this example, I intentionally left the templateUrl in the decorator blank. Normally, it would have the value card.component.html. But our component has two templates that we made above. Next up we'll figure out one way to change between the two template files.

Conditionally Changing the Template File

Depending on how long you've been using Angular, you may or may not be familiar with the environment.ts file. This file is used at build time for your Angular application. You can read a little bit more about the difference between build time and runtime configuration in Angular apps in this article. In short, the values in the environment.ts file can be used throughout the application, but if any values change the app needs to be built again for the changes to take place. When an Angular app is generated by the CLI, there is an environment.ts file that is used for local development by default, and an environment.prod.ts file which is used when the app is built for production environments. When the files are used is determined in the angular.json file.

Now that we have briefly covered what the environment.ts file is and how to use it, we'll talk about using it for our scenario. I figured I could use this environment file to determine which template the app should use when building the app. If I added a value to the file, such as useTailwindTemplate, I could use that to determine which template to use. Let's look at the component's TypeScript file again.

// card.component.ts

import { environment } from 'src/environments/environment';

@Component({
    selector: 'app-card',
    templateUrl: environment.useTailwindTemplate ? 'card-tailwind.component.html' : 'card-bootstrap.component.html',
    styleUrls: ['card.component.scss']
})

Now the value of templateUrl, is set based off the value of the attribute in the environment file. To test it out, you can build the application with the environment.useTailwindTemplate attribute set to true and open the app in your browser. You should see the Tailwind card on the page. If you set the value to false, you should see the Bootstrap card on the page.

Conclusion

The example here is simple, and potentially excessive for a simple card component. But imagine your component has some complicated logic that you don't want to repeat in multiple components. Any time you have multiple components with the same logic that needs to be repeated, the two components get out of sync quickly. In those cases, this may be a good option. In our case, we will be using this option to use Tailwind in one app and Bootstrap in another. Also, it will allow us to incrementally convert the Bootstrap app to Tailwind. The logic can remain in a single TypeScript file, while being able to use multiple templates.

This is just one way you could conditionally swap out the template file for a component. For some other ideas, you can check out this Twitter thread.

Getting Started with Deno

Learn how to start using Deno with my brand new course!

Buy the Course