On day 24, I create an Alert Bar component to show or hide the close button, apply a new style to the alerts, and change their direction.
Create a Close SVG Icon
Vue 3 application
Create an icons/CloseIcon.vue
Display the close icon in the alert component
<script setup lang="ts">
import CloseIcon from '@/icons/CloseIcon.vue'
</script>
... omitted to save space ...
Import the CloseIcon
and add the
within the tags to render the close icon on the button.
SvelteKit application
Create a lib/icons/close-icon.svelte
Display the close icon in the alert component
<script lang="ts">
import CloseIcon from '$lib/icons/close-icon.svelte'
</script>
... omitted to save space ...
Import the CloseIcon
and add the
in the HTML template to render the close icon on the button.
Angular 20 application
Create a CloseIconComponent
@Component({
selector: 'app-close-icon',
template: `
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CloseIconComponent {}
Display the close icon in the alert component
import { NgComponentOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core';
import { AlertType } from '../alert.type';
import { CloseIconComponent, ErrorIconComponent, InfoIconComponent, SuccessIconComponent, WarningIconComponent } from '../icons/icon.component';
@Component({
selector: 'app-alert',
imports: [NgComponentOutlet, CloseIconComponent],
template: `
... omitted to save space ...
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {}
Import the CloseIconComponent
and register it to the imports
array of the @Component
decorator.
In the inline template, use the app-close-icon
selector to render the close icon on the button.
Add Show/Hide checkbox to the AlertBar Component
Vue 3 application
Since Vue 3.4, the recommended approach of two-way data binding is using the defineModel
macro.
<script setup lang="ts">
const hasCloseButton = defineModel<boolean>('hasCloseButton', { default: true })
</script>
Declare a defineModel
and assign the value to the hasCloseButton
ref. { default: true }
means the default value of the ref is true The checkbox is checked and the alert component should show a close button.
In the template, the v-model
directive is added to the checkbox input and binded to hasCloseButton
. The hasCloseButton
enables 2-way data binding and passes the value up to the parent component, which is the AlertList
component.
SvelteKit application
In Svelte 5, two-way binding is done with the $bindable
function. Moreover, $bindable
must be used with $props
together. We will update the Prop
type to add hasCloseButton
and destructure the property from the $prop()
call.
type Props = {
hasCloseButton: boolean;
}
let {
hasCloseButton = $bindable(),
}: Props = $props();
In the template, the bind:checked
attribute of the checkbox is binded to hasCloseButton
. The hasCloseButton
enables 2-way data binding and passes the value up to the parent component, which is the AlertList
component.
Angular 20 application
In Angular, two-way binding is achieved by the the model
Writable signal.
import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-alert-bar',
imports: [],
template: `
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertBarComponent {
hasCloseButton = model<boolean>(true);
}
Declare a model
and assign the value to the hasCloseButton
signal. The default value of model
is true, so the checkbox is checked and the alert component should show a close button.
Pass hasCloseButton up to the AlertList Component
Vue 3 application
<script setup lang="ts">
import { computed, ref } from 'vue';
const hasCloseButton = ref(true)
</script>
Declare a hasCloseButton
ref in the AlertList
component to receive the value from the child.
Alert Components (Vue ver.)
v-model:hasCloseButton="hasCloseButton"
/>
A Vue component allows multiple v-model
bindings.
v-model:hasCloseButton="hasCloseButton"
binds the child’s hasCloseButton
to the hasCloseButton
ref.
SvelteKit application
<script lang="ts">
import AlertBar from './alert-bar.svelte';
let hasCloseButton = $state(true);
</script>
Declare a hasCloseButton
rune in the AlertList
component to receive the value from the child.
{configs}
bind:hasCloseButton={hasCloseButton}
/>
bind:hasCloseButton={hasCloseButton}
means the AlertList
component listens to what the hasCloseButton
prop of the AlerBar
component has to say.
Angular 20 application
import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';
@Component({
selector: 'app-alert-list',
imports: [AlertBarComponent],
template: `
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
hasCloseButton = signal<boolean>(true);
}
Declare a hasCloseButton
signal in the AlertList
component to receive the value from the child.
Use banana-in-the-box syntax, [(expression)]
, to perform two-way binding for the AlertList
and AlertBar
components.
Show/Hide Close Button in the Alert Component
Vue 3 application
const hasCloseButton = ref(true)
const alertConfig = computed(() => ({
hasCloseButton: hasCloseButton.value,
}))
Define a alertConfig
computed ref to create an object to store the value of hasCloseButton.value
.
v-for="{ type, message } in alerts"
class="mb-[0.75rem]"
:key="type"
:type="type"
:alertConfig="alertConfig">
{{ message }}
Pass the alertConfig
to the alertConfig
prop of the Alert
component.
<script setup lang="ts">
import { computed, ref } from 'vue'
import CloseIcon from '@/icons/CloseIcon.vue'
type Props = {
alertConfig: {
hasCloseButton: boolean
},
type: string
}
const { type, alertConfig } = defineProps<Props>()
... omit the template codes ...
</script>
In the Alert
component, add the new alertConfig
property to the Prop
type. Destructure the alertConfig
from the defineProps
macro.
role="alert" :class="alertClasses" v-if="!closed">
:is="icon" />
v-if="alertConfig.hasCloseButton">
If alertConfig.hasCloseButton
is true, the close button will be shown. Otherwise, it is hidden.
SvelteKit application
<script lang="ts">
import AlertBar from './alert-bar.svelte';
import Alert from './alert.svelte';
import type { AlertMessage } from './alert.type';
let hasCloseButton = $state(true);
</script>
Declare a hasCloseButton
rune in the AlertList
component to receive the value from the child.
{#each alerts as alert (alert.type) }
{alert} {hasCloseButton} />
{/each}
Pass the hasCloseButton
rune to the hasCloseButton
prop of the Alert
component.
{configs}
bind:hasCloseButton={hasCloseButton}
/>
bind:hasCloseButton={hasCloseButton}
means the AlertList
component listens to what the hasCloseButton
prop of the AlertBar
component has to say.
<script lang="ts">
type Props = {
... other properties ...
hasCloseButton: boolean;
}
const {
hasCloseButton,
}: Props = $props();
</script>
In the Alert
component, add a hasCloseButton
property to the Prop
type and destructure the property from the object.
{#if !closed}
role="alert" class={alertClasses}>
... omit the template code...
{#if hasCloseButton}
{/if}
{/if}
If hasCloseButton
is true, the button is rendered. Otherwise, the button is hidden.
Angular 20 application
import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';
@Component({
selector: 'app-alert-list',
imports: [AlertBarComponent],
template: `
@for (alert of alerts(); track alert.type) {
{{ alert.message }}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
hasCloseButton = signal<boolean>(true);
alertConfig = computed(() => ({
hasCloseButton: this.hasCloseButton(),
}));
}
Declare a hasCloseButton
signal in the AlertList
component to receive the value from the child.
Use banana-in-the-box syntax, [(expression)]
, to perform two-way binding for the AlertList
and AlertBar
components.
Define a alertConfig
computed signal to create an object to store the value of the hasCloseButton
signal.
Assign the value of the alertConfig
computed signal to the input signal of AlertComponent
.
import { NgComponentOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core';
import { AlertType } from '../alert.type';
import { CloseIconComponent, ErrorIconComponent, InfoIconComponent, SuccessIconComponent, WarningIconComponent } from '../icons/icon.component';
@Component({
selector: 'app-alert',
imports: [NgComponentOutlet, CloseIconComponent],
template: `
@if (!closed()) {
... omit the template codes ...
@if (alertConfig().hasCloseButton) {
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {
type = input.required<AlertType>();
alertConfig = input.required<{
hasCloseButton: boolean
}>();
}
In the AlertComponent
, declare an alertConfig
required input signal.
If alertConfig().hasCloseButton
is true, the button is rendered. Otherwise, the button is hidden.
Next, we will repeat the same procedure to add style and direction dropdown lists to change the styling of the alert component.
Add Alert Style and Direction dropdown lists
Vue 3 application
<script setup lang="ts">
type Props = {
config: {
styleLabel: string
styles: { text: string, value: string }[]
directionLabel: string
directions: { text: string, value: string }[]
},
}
const props = defineProps<Props>()
const { config } = props
</script>
Declare a Prop in the AlertBar
component to accept the style label, style dropdown list, direction label, and direction dropdown list.
const style = defineModel<string>('style', { default: 'color' })
const direction = defineModel<string>('direction', { default: 'horizontal' })
Declare style
and direction
ref for two-way data binding. The default value of style
is ‘color’ and the default value of direction
is ‘horizontal’.
class="mb-[0.75rem]">
{{ config.styleLabel }}Ā Ā
{{ config.directionLabel }}Ā Ā
Use v-for
to iterate config.styles
and config.directions
to populate the option items of the select dropdown.
SvelteKit application
Add configs
, style
and direction
to the Props
type.
Destructure style
and direction
from $props()
and bind them to the AlertList
component using $bindable
.
class="mb-[0.75rem]">
{ configs.styleLabel }Ā Ā { style }
{ configs.directionLabel }Ā Ā
bind:value={style}
binds style
rune to the style dropdown. bind:value={direction}
bind the direction
rune to the direction dropdown.
Use #each
to iterate the styles
and directions
lists to populate the dropdown lists.
Angula 20 application
@Component({
selector: 'app-alert-bar',
imports: [FormsModule],
template: `... inline template explained below ...`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertBarComponent {
config = input.required<{
styleLabel: string
styles: { text: string, value: string }[]
directionLabel: string
directions: { text: string, value: string }[]
}>();
hasCloseButton = model<boolean>(true);
style = model<string>('color');
direction = model<string>('horizontal');
}
The AlertBarComponent
declares a required input signal for the labels and dropdown lists.
Similarly, style
and direction
are declared model
signals for two-way binding.
The component also imports FormsModule
to bind the model
to the [(ngModel)]
in the inline template.
@let c = config();
class="mb-[0.75rem]">
{{ c.styleLabel }}Ā Ā
{{ c.directionLabel }}Ā Ā
[(ngModel)]="style"
binds style
to the style dropdown. Similarly, [(ngModel)]="direction"
binds direction
to the direction dropdown.
The @for
loop iterates the styles and directions lists to display the value/text items.
Perform two-way binding
Vue 3 Application
The config
ref defines the labels and items of the style and direction.
<script setup lang="ts">
import type { AlertType } from '@/types/alert-type';
import { computed, ref } from 'vue';
import Alert from './Alert.vue';
import AlertBar from './AlertBar.vue';
const hasCloseButton = ref(true)
const style = ref('color');
const direction = ref('horizontal')
const alertConfig = computed(() => ({
style: style.value,
direction: direction.value,
hasCloseButton: hasCloseButton.value,
}))
const config = ref({
styleLabel: "Alert styles:",
styles: [
{ text: 'Default', value: 'color' },
{ text: 'Soft', value: 'soft' },
{ text: 'Outline', value: 'outline' },
{ text: 'Dash', value: 'dash' }
],
directionLabel: "Alert direction:",
directions: [
{ text: 'Horizontal', value: 'horizontal' },
{ text: 'Vertical', value: 'vertical' },
]
})
</script>
Similar to hasCloseButton
, style
and direction
pass the values from the AlertBar
component to the AlertList
component. Then, the alertConfig
computed ref returns the value of style
and direction
. The alertConfig
passes the new values to style the Alert
component.
:config="config"
v-model:hasCloseButton="hasCloseButton"
v-model:style="style"
v-model:direction="direction"
/>
The AlertBar
component receives the config
prop. The v-model:style="style"
binds the style
ref of the AlertBar
component to style
ref of the AlertList
component. Similarly, v-model:direction="direction"
binds the direction
ref of the AlertBar
component to the direction
ref of the AlertList
component.
SvelteKit Application
The labels and items of style and direction are defined in the config
rune.
<script lang="ts">
import AlertBar from './alert-bar.svelte';
import Alert from './alert.svelte';
const configs = $state(... same object...);
let hasCloseButton = $state(true);
let style = $state('color');
let direction = $state('horizontal');
</script>
Similar to hasCloseButton
, add style
and direction
runes to the AlertList
component.
{configs}
bind:hasCloseButton={hasCloseButton}
bind:style={style}
bind:direction={direction}
/>
The AlertBar
component receives the config
prop to populate the dropdown. The AlertBar
uses the bind
syntax to pass the style
and direction
to the AlertList
component.
{#each filteredNotification as alert (alert.type) }
{alert} {alertMessage} {notifyClosed} {hasCloseButton} {style} {direction} />
{/each}
Pass the new prop values, style
and direction
, to the Alert
component.
Angular 20 Application
The labels and items of style and direction are defined in the config
signal.
import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertComponent } from '../alert/alert.component';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';
@Component({
selector: 'app-alert-list',
imports: [AlertComponent, AlertBarComponent],
template: `...inline template shown below ...`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
style = signal<string>('color');
direction = signal<string>('horizontal');
hasCloseButton = signal<boolean>(true);
config = signal({... same object ... });
alertConfig = computed(() => ({
hasCloseButton: this.hasCloseButton(),
style: this.style(),
direction: this.direction()
}));
}
Similar to hasCloseButton
, add style
and direction
signal to the AlertList
component. The alertConfig
computed signal returns the value of the style
and direction
signals.
[config]="config()"
[(style)]="style"
[(direction)]="direction"
[(hasCloseButton)]="hasCloseButton"
/>
The AlertBar
component receives the config
input to populate the dropdown. The AlertBar
uses the banana-in-the-box syntax [(expression)]
to bind the style
and direction
models of the AlertBar
component to the style
and direction
signals of the AlertList
component.
[(style)]="style"
– The first style
is the style
model of the AlertBarComponent
and the second style
is the style
signal of the AlertListComponent
.
[(direction)]="direction"
– The first direction
is the direction
model of the AlertBarComponent
and the second direction
is the direction
signal of the AlertListComponent
.
Apply Style and Direction to Alert Component
Vue 3 Application
type Props = {
alertConfig: {
hasCloseButton: boolean
style: string
direction: string
},
type: string
}
const { type, alertConfig } = defineProps<Props>()
In the Props
type of the Alert
component, add style
and direction
to the alertConfig
property.
const alertColor = computed(() => ({
info: 'alert-info',
warning: 'alert-warning',
error: 'alert-error',
success: 'alert-success'
}[type]))
const alertStyle = computed(() => ({
color: '',
dash: 'alert-dash',
soft: 'alert-soft',
outline: 'alert-outline'
}[alertConfig.style]))
const alertDirection = computed(() => ({
horizontal: 'alert-horizontal',
vertical: 'alert-vertical',
}[alertConfig.direction]))
const alertClasses = computed(() => `alert ${alertColor.value} ${alertStyle.value} ${alertDirection.value}`)
Create the alertStyle
computed ref to derive the style class. Create the alertDirection
computed ref to derive the direction class.
The alertClasses
computed ref concatenates the style classes of the alert component.
role="alert" :class="alertClasses" v-if="!closed">
The alertClasses
binds the new styles to the class
attribute to change the direction, border style and color.
SvelteKit Application
type Props = {
hasCloseButton: boolean;
style: string;
direction: string;
}
const {
hasCloseButton,
direction,
style
}: Props = $props();
In the Props
type of the Alert
component, add style
and direction
properties.
const alertColor = $derived.by(() => ({
info: 'alert-info',
success: 'alert-success',
warning: 'alert-warning',
error: 'alert-error',
}[alert.type]));
const alertDirection = $derived.by(() => ({
horizontal: 'alert-horizontal',
vertical: 'alert-vertical',
}[direction]));
const alertStyle = $derived.by(() => ({
color: '',
soft: 'alert-soft',
outline: 'alert-outline',
dash: 'alert-dash',
}[style]));
const alertClasses = $derived(`alert ${alertColor} ${alertDirection} ${alertStyle} mb-[0.75rem]`);
Create the alertStyle
derived rune to derive the style class. Create the alertDirection
derived rune to derive the direction class.
The alertClasses
derived rune concatenates the style classes of the alert component.
{#if !closed}
role="alert" class={alertClasses}>
{/if}
The alertClasses
binds the new styles to the class
attribute to change the direction, border style and color.
Angular 20 Application
@Component({
selector: 'app-alert',
imports: [NgComponentOutlet, CloseIconComponent],
template: `... inline template ...`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {
type = input.required<AlertType>();
alertConfig = input.required<{
hasCloseButton: boolean
style: string
direction: string
}>();
alertColor = computed(() => {
return {
info: 'alert-info',
warning: 'alert-warning',
error: 'alert-error',
success: 'alert-success'
}[this.type()]
});
alertStyle = computed(() => {
return {
color: '',
dash: 'alert-dash',
soft: 'alert-soft',
outline: 'alert-outline'
}[this.alertConfig().style]
});
alertDirection = computed(() => {
return {
horizontal: 'alert-horizontal',
vertical: 'alert-vertical',
}[this.alertConfig().direction]
});
alertClasses = computed(() => `alert ${this.alertColor()} ${this.alertStyle()} ${this.alertDirection()}`);
}
Add style
and direction
to the alertConfig
required input signal.
Create the alertStyle
computed signal to derive the style class. Create the alertDirection
computed signal to derive the direction class.
The alertClasses
computed signal concatenates the style classes of the alert component.
@if (!closed()) {
role="alert" class="mb-[0.75rem]" [class]="alertClasses()">
}
The alertClasses
binds the new styles to the class
attribute to change the direction, border style and color.
Now, users can select values in the AlertBar
component to show/hide the close button, change the border style, direction, and color of the alerts.