Skip to main content
Version: next

Cross-component manipulation

Components are ordinary HTML elements, so they can be retrieved and manipulated using querySelector, etc. This includes from other components.

This example shows a use cases where you would want component A to manipulate to component B, and how to do it safely.

Checking for presence of another component

Components are ordinary elements, so you can check if a component is present by attempting to find it in the document:

ts
if (document.getElementById<CookieBanner>('cookie-banner')) {
// Cookie banner exists on this page
}
ts
if (document.getElementById<CookieBanner>('cookie-banner')) {
// Cookie banner exists on this page
}

Cross-component events

One of the most useful cases for this is having one component subscribe to an event on another component.

In this example, we want a popup component to be displayed whenever the first time the cart popover component is dismissed.

src/components/CartPopover.ts
ts
import { Component, SimpleEvent } from '@eastsideco/salvo-ts';
import { ThemeComponent } from '../theme/ThemeComponent';
@Component
export class CartPopover extends ThemeComponent {
private _onStateChange = new SimpleEvent<{ open: boolean; }>();
get onStateChange() {
return this._onStateChange.asEvent();
}
// ... cart popover logic ...
dismissPopover() {
// ... hide the popover ...
// Raise an event to notify that our state changed
this._onStateChange.dispatch({
open: false
});
}
}
src/components/CartPopover.ts
ts
import { Component, SimpleEvent } from '@eastsideco/salvo-ts';
import { ThemeComponent } from '../theme/ThemeComponent';
@Component
export class CartPopover extends ThemeComponent {
private _onStateChange = new SimpleEvent<{ open: boolean; }>();
get onStateChange() {
return this._onStateChange.asEvent();
}
// ... cart popover logic ...
dismissPopover() {
// ... hide the popover ...
// Raise an event to notify that our state changed
this._onStateChange.dispatch({
open: false
});
}
}
src/components/OfferPopup
ts
import { Component, SimpleEvent } from '@eastsideco/salvo-ts';
import { ThemeComponent } from '../theme/ThemeComponent';
// IMPORTANT: You MUST include an import for a component if you use it,
// even if you don't directly reference this value anywhere in your code.
import { CartPopover } from './CartPopover';
@Component
export class OfferPopup extends ThemeComponent {
// We only want to show the first time this happens
private hasShown = false;
init() {
// Find the cart popover component
const cartPopover = document.querySelector<CartPopover>('cart-popover');
if (!cartPopover) {
throw new Error(`OfferPopup expects a CartPopover to be present on the page`);
}
// Subscribe to the state change event
this.$subscribe(cartPopover.onStateChange, ({ open }) => {
if (!open && !hasShown) {
hasShown = true;
// ... show offer popup ...
}
});
}
}
src/components/OfferPopup
ts
import { Component, SimpleEvent } from '@eastsideco/salvo-ts';
import { ThemeComponent } from '../theme/ThemeComponent';
// IMPORTANT: You MUST include an import for a component if you use it,
// even if you don't directly reference this value anywhere in your code.
import { CartPopover } from './CartPopover';
@Component
export class OfferPopup extends ThemeComponent {
// We only want to show the first time this happens
private hasShown = false;
init() {
// Find the cart popover component
const cartPopover = document.querySelector<CartPopover>('cart-popover');
if (!cartPopover) {
throw new Error(`OfferPopup expects a CartPopover to be present on the page`);
}
// Subscribe to the state change event
this.$subscribe(cartPopover.onStateChange, ({ open }) => {
if (!open && !hasShown) {
hasShown = true;
// ... show offer popup ...
}
});
}
}
static/template/test.page.liquid
html
<!-- These can be anywhere on the page: -->
<cart-popover>
<!-- ... -->
</cart-popover>
<offer-popup>
<!-- ... -->
</offer-popup>
static/template/test.page.liquid
html
<!-- These can be anywhere on the page: -->
<cart-popover>
<!-- ... -->
</cart-popover>
<offer-popup>
<!-- ... -->
</offer-popup>

$subscribe is used to ensure the event is unregistered if OfferPopup is removed. See the events guide for more information on custom events.

Import component dependencies

If you are accessing a component through DOM it's easy to do so without directly referencing the component, however if you fail to actually import the component into your class, the compiler has no way of knowing you may access that component at runtime.

This can result in errors where non-native-HTML features of components you retrieve fail to load, for example:

ts
const cartPopover = document.querySelector('cart-popover');
cartPopover.onStateChange // undefined
ts
const cartPopover = document.querySelector('cart-popover');
cartPopover.onStateChange // undefined

In this example, cartPopover is not actually a CartPopover component yet, because it hasn't loaded (it's an unknown HTMLElement placeholder.)


To inform the compiler that you intend to access CartPopover you should include an import to it. Getting into the habit of using the typed versions of querySelector<T> is a great way to make sure you always do this, as it will force you to include the import:

ts
// Silent error - may fail at runtime because cartPopover may not be initialized
const cartPopover = document.querySelector('cart-popover');
// ERR - CartPopover is not defined
const cartPopover = document.querySelector<CartPopover>('cart-popover');
// OK
import { CartPopover } from './CartPopover';
const cartPopover = document.querySelector<CartPopover>('cart-popover');
// Also OK
import { CartPopover } from './CartPopover';
const cartPopover: CartPopover|null = document.querySelector('cart-popover');
ts
// Silent error - may fail at runtime because cartPopover may not be initialized
const cartPopover = document.querySelector('cart-popover');
// ERR - CartPopover is not defined
const cartPopover = document.querySelector<CartPopover>('cart-popover');
// OK
import { CartPopover } from './CartPopover';
const cartPopover = document.querySelector<CartPopover>('cart-popover');
// Also OK
import { CartPopover } from './CartPopover';
const cartPopover: CartPopover|null = document.querySelector('cart-popover');