Table of Contents
- Define Component Event to Emit Active Plan
- Add Selected Prop in the CoffeePlan Component
- PlanPicker notifies other CoffeePlan to unselect
- Conclusions
- Resources
- Github Repositories
On day 16, I put a border around a coffee plan when it was selected. Other coffee plans were inactive and lost the border. The CoffeePlan
component emitted the active name to the PlanPicker
component so that it could notify other coffee plans to remove the border.
Define Component Event to Emit Active Plan
- Vue 3 application
const emit = defineEmits<{
(e: 'selectedPlan', name: string): void
}>()
function selectPlan() {
emit('selectedPlan', props.name)
}
The CoffeePlan
component defines a custom event that emits the name to the PlanPicker
component.
The selectedPlan
function uses the selectedPlan
event to emit the plan name to the parent component. The PlanPicker
component receives the active plan and can notify other plans that they are no longer active.
- SvelteKit application
<script lang="ts">
interface Props {
name: string;
selectedPlan: (name: string) => void;
}
let { name = 'Default Plan', selectedPlan }: Props = $props();
const handleSelectPlan = () => selectedPlan(name);
</script>
The CoffeePlan
component extracts the selectedPlan
function from $props()
. The PlanPicker
component must provide the prop callback for the CoffeePlan
component to call in the handleSelectPlan
function.
The handleSelection
invokes the selectedPlan
function with the plan name.
- Angular 19 application
@Component({
...
})
export class CoffeePlanComponent {
name = input('Default Plan');
selectedPlan = output<string>();
selectPlan() {
this.selectedPlan.emit(this.name());
}
}
The CoffeePlanComponent
declares a selectedPlan
output that emits the coffee name to the PlanPickerComponent
. The name
is a signal input of type string.
When the selectPlan
method is invoked, the selectedPlan
outputs the result of the name
getter to the PlanPickerComponent
.
Add Selected Prop in the CoffeePlan Component
- Vue 3 application
<script setup lang="ts">
const props = defineProps({
selected: {
type: Boolean,
default: false,
},
})
</script>
Add selected
to the CoffeePlan
's props. When selected
is true, the plan is active and it has a border. When selected
is false, the border is removed.
<template>
<div class="plan" @click="selectPlan" :class="{ 'active-plan': selected }">
<div class="description">
<span class="title"> {{ name }} </span>
</div>
</div>
</template>
The div
element is bound to the active-plan
class dynamically. When selected
is true, the CSS class is enabled. Otherwise, the class is removed.
- SvelteKit application
<script lang="ts">
interface Props {
name: string;
selectedPlan: (name: string) => void;
selected: boolean;
}
let { name = 'Default Plan', selectedPlan, selected }: Props = $props();
const handleSelectPlan = () => selectedPlan(name);
</script>
Similarly, the selected
flag is extracted from $props
. Moreover, the Props
interface has a selected
property of type boolean.
<div onclick={handleSelectPlan} class={['plan', selected && 'active-plan']}>
<div class="description">
<span class="title"> {name} </span>
</div>
</div>
When selected
is true, active-plan
is part of the class list. Otherwise, the class list does not have the class
- Angular 19 application
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
@Component({
selector: 'app-coffee-plan',
template: `
<div class="plan" (click)="selectPlan()" [class]="{ 'active-plan': selected() }">
<div class="description">
<span class="title"> {{ name() }} </span>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CoffeePlanComponent {
name = input('Default Plan');
selected = input(false);
selectedPlan = output<string>();
selectPlan() {
this.selectedPlan.emit(this.name());
}
}
selected
is a signal input that is initialized to false
.
The class
attribute of the div
element is bound to an object. active-plan
class is enabled when the getter function of selected
returns true. Otherwise, the class is removed from the element and border is not seen.
PlanPicker notifies other CoffeePlan to unselect
- Vue 3 application
// PlanPicker
<script setup lang="ts">
const selectedPlan = ref('')
function handleSelectPlan(name: string) {
selectedPlan.value = name
}
function isSelected(plan: string) {
return selectedPlan.value === plan
}
</script>
<template>
<div class="plans">
<CoffeePlan
v-for="plan in plans"
:key="plan"
:name="plan"
:selected="isSelected(plan)"
@selectedPlan="handleSelectPlan"
>
</CoffeePlan>
</div>
</template>
When CoffeePlan
emits the selectedPlan
event, the handleSelectedPlan
updates the selectedPlan
ref. The isSelected
function determines whether or not a coffee plan is selected. Then, the function assigns the result to the selected
input of the CoffeePlan
component. When the plan is selected, the CSS adds a border to it. Otherwise, the coffee plan does not display the border.
- SvelteKit application
<script lang="ts">
let selectedCoffeePlan = $state('');
const selectedPlan = (name: string) => (selectedCoffeePlan = name);
const isSelected = (plan: string) => selectedCoffeePlan === plan;
</script>
<div class="plans">
{#each plans as plan (plan)}
<CoffeePlan name={plan} {selectedPlan} selected={isSelected(plan)} />
{/each}
</div>
When CoffeePlan
emits the selectedPlan
event, the selectedPlan
updates the selectedCoffeePlan
rune. The isSelected
function determines whether or not a coffee plan is selected. Then, the function assigns the result to the selected
input of the CoffeePlan
component. When the plan is selected, the CSS adds a border to it. Otherwise, the coffee plan does not display the border.
- Angular application
@Component({
selector: 'app-plan-picker',
})
export class PlanPickerComponent {
selectedPlan = signal('');
handleSelectPlan(name: string) {
this.selectedPlan.set(name);
}
isPlanSelected(planName: string): boolean {
return this.selectedPlan() === planName;
}
}
<div class="plans">
@for (plan of plans(); track plan) {
<app-coffee-plan
[name]="plan"
(selectedPlan)="handleSelectPlan($event)"
[selected]="isPlanSelected(plan)"
/>
}
</div>
When PlanPickerComponent
emits the selectedPlan
event, the handleSelectPlan
method updates the selectedPlan
signal. The isPlanSelected
method determines whether or not a coffee plan is selected. Then, the method assigns the result to the selected
input of the CoffeePlan
component. When the plan is selected, the CSS adds a border to it. Otherwise, the coffee plan does not display the border.
Conclusions
We have successfully added new prop and component event to communicate between the CoffeePlan
component and thePlanPicker
component. The PlanPicker
derives the value of theselected
and passes it to other CoffeePlan
components to enable or disable CSS class dynamically.