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:
tsif (document.getElementById<CookieBanner>('cookie-banner')) {// Cookie banner exists on this page}
tsif (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.tstsimport { Component, SimpleEvent } from '@eastsideco/salvo-ts';import { ThemeComponent } from '../theme/ThemeComponent';@Componentexport 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 changedthis._onStateChange.dispatch({open: false});}}
src/components/CartPopover.tstsimport { Component, SimpleEvent } from '@eastsideco/salvo-ts';import { ThemeComponent } from '../theme/ThemeComponent';@Componentexport 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 changedthis._onStateChange.dispatch({open: false});}}
src/components/OfferPopuptsimport { 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';@Componentexport class OfferPopup extends ThemeComponent {// We only want to show the first time this happensprivate hasShown = false;init() {// Find the cart popover componentconst 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 eventthis.$subscribe(cartPopover.onStateChange, ({ open }) => {if (!open && !hasShown) {hasShown = true;// ... show offer popup ...}});}}
src/components/OfferPopuptsimport { 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';@Componentexport class OfferPopup extends ThemeComponent {// We only want to show the first time this happensprivate hasShown = false;init() {// Find the cart popover componentconst 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 eventthis.$subscribe(cartPopover.onStateChange, ({ open }) => {if (!open && !hasShown) {hasShown = true;// ... show offer popup ...}});}}
static/template/test.page.liquidhtml<!-- These can be anywhere on the page: --><cart-popover><!-- ... --></cart-popover><offer-popup><!-- ... --></offer-popup>
static/template/test.page.liquidhtml<!-- 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.
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:
tsconst cartPopover = document.querySelector('cart-popover');cartPopover.onStateChange // undefined
tsconst 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 initializedconst cartPopover = document.querySelector('cart-popover');// ERR - CartPopover is not definedconst cartPopover = document.querySelector<CartPopover>('cart-popover');// OKimport { CartPopover } from './CartPopover';const cartPopover = document.querySelector<CartPopover>('cart-popover');// Also OKimport { CartPopover } from './CartPopover';const cartPopover: CartPopover|null = document.querySelector('cart-popover');
ts// Silent error - may fail at runtime because cartPopover may not be initializedconst cartPopover = document.querySelector('cart-popover');// ERR - CartPopover is not definedconst cartPopover = document.querySelector<CartPopover>('cart-popover');// OKimport { CartPopover } from './CartPopover';const cartPopover = document.querySelector<CartPopover>('cart-popover');// Also OKimport { CartPopover } from './CartPopover';const cartPopover: CartPopover|null = document.querySelector('cart-popover');