DEV Community

Connie Leung
Connie Leung

Posted on

Day 23 - Alert Component Part 2 - Dynamic Rendering of SVG Icons

Day 23 - Alert Component Part 2 - Dynamic Rendering of SVG Icons

Table of Contents

Component Fundamentals with JavaScript Frameworks

On day 23, I render the alert SVG icons dynamically because dynamic rendering is more maintainable, scalable and efficient.

I create components for the SVG icons and render the appropriate icon component by the alert type.

Framework Method
Vue 3 :is attribute with the
Svelte 5 Dynamic component is capitalized
Angular ngComponentOutlet structural directive enables dynamic rendering of the components

Create Icon Components

Vue 3 application

Create an icons directory under src

  • InfoIcon.vue
<template>
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
    </svg>
</template>  
Enter fullscreen mode Exit fullscreen mode
  • SuccessIcon.vue
<template>
  <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
  </svg>
</template>
Enter fullscreen mode Exit fullscreen mode
  • WarningIcon.vue
<template>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
    </svg>
</template> 
Enter fullscreen mode Exit fullscreen mode
  • ErrorIcon.vue
<template>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
    </svg>
</template>  
Enter fullscreen mode Exit fullscreen mode

In the .vue files, the SVG icon is defined in the <template> element.


SvelteKit application

Create an icons directory under src/lib

  • info-icon.svelte
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> 
Enter fullscreen mode Exit fullscreen mode
  • success-icon.svelte
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Enter fullscreen mode Exit fullscreen mode
  • warning-icon.svelte
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
Enter fullscreen mode Exit fullscreen mode
  • error-icon.svelte
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Enter fullscreen mode Exit fullscreen mode

In the .svelte files, the SVG icon is defined in the file.


Angular 20 application

Create an icons directory under src/app

  • icon.component.ts
@Component({
  selector: 'app-info-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InfoIconComponent {}
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-success-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SuccessIconComponent {}
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-warning-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WarningIconComponent {}
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-error-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ErrorIconComponent {}
Enter fullscreen mode Exit fullscreen mode

In the component classes, the SVG icon is defined as an inline template because the code is short. It can be seen in the template property of the @Component decorator.


Dynamic Rendering of the icon in the Alert Component

The goal is to delete the conditional branches from the Alert component.

Vue 3 application

<script setup lang="ts"> 
    import InfoIcon from '@/icons/InfoIcon.vue'
    import SuccessIcon from '@/icons/SuccessIcon.vue'
    import WarningIcon from '@/icons/WarningIcon.vue'
    import ErrorIcon from '@/icons/ErrorIcon.vue'

    const icon = computed(() => ({
        info: InfoIcon,
        warning: WarningIcon,
        error: ErrorIcon,
        success: SuccessIcon,
    }[type]))
</script>
Enter fullscreen mode Exit fullscreen mode

Import the Icon components to the Alert component. Then, define an icon computed ref that derives the icon to display. The dictionary indexes the type prop to determine the appropriate icon.

<component :is="icon" />    
Enter fullscreen mode Exit fullscreen mode

The v-if and v-if-else are replaced with one line of code.


SvelteKit application

<script lang="ts">
    import InfoIcon from '$lib/icons/info-icon.svelte'
    import ErrorIcon from '$lib/icons/error-icon.svelte'
    import SuccessIcon from '$lib/icons/success-icon.svelte'
    import WarningIcon from '$lib/icons/warning-icon.svelte'

    const Icon = $derived.by(() => ({
        info: InfoIcon,
        success: SuccessIcon,
        warning: WarningIcon,
        error: ErrorIcon,
    }[alert.type]));
</script>
Enter fullscreen mode Exit fullscreen mode

Import the Icon components to the Alert component. Then, define an Icon derived rune that returns the icon to display. The dictionary indexes the alert.type prop to determine the appropriate icon.

   <Icon ></Icon> 
Enter fullscreen mode Exit fullscreen mode

The if-else-if control flow is replaced with <Icon></Icon>. The dynamic component's name must be capitalized; therefore, the derived rune is Icon.


Angular 20 application

import { NgComponentOutlet } from '@angular/common';
import { ErrorIconComponent, InfoIconComponent, SuccessIconComponent, WarningIconComponent } from '../icons/icon.component';

@Component({
  selector: 'app-alert',
  imports: [NgComponentOutlet],
  template: `
    <ng-container [ngComponentOutlet]="icon()" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {
  icon = computed(() => {
    return {
        info: InfoIconComponent,
        warning: WarningIconComponent,
        error: ErrorIconComponent,
        success: SuccessIconComponent,
    }[this.type()];
  });
}
Enter fullscreen mode Exit fullscreen mode

Import the Icon components to the AlertComponenet. Then, define an icon computed signal that derives the icon to display. The dictionary indexes the type input signal to determine the appropriate icon.

The if-else-if control flow is replaced with <ng-container [ngComponentOutlet]="icon()" />.


The template is cleaned up to have one line of code to render the dynamic component.
We have successfully rendered the icon component in the alert in Vue, Svelte and Angular frameworks.

Github Repositories

Github Pages

Resources

Top comments (0)