---
title: Bitovi Academy - Dispatching events
description: Learn how to use custom events to provide information on the *state* of your component to parent elements.
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting?hsLang=en-us)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting?hsLang=en-us)
- ![Project management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project management](https://www.bitovi.com/services/agile-project-management-consulting?hsLang=en-us)
- ![Product design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product design](https://www.bitovi.com/services/product-design-consulting?hsLang=en-us)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services?hsLang=en-us)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting?hsLang=en-us)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers?hsLang=en-us)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting?hsLang=en-us)
- [Design Systems](https://www.bitovi.com/services/axure-figma-migration?hsLang=en-us)
- [React](https://www.bitovi.com/services/frontend/react-consulting?hsLang=en-us)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting?hsLang=en-us)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native?hsLang=en-us)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting-1?hsLang=en-us)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study?hsLang=en-us)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign?hsLang=en-us)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work?hsLang=en-us)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source?hsLang=en-us)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about perfecting software delivery
  
  ](https://bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships?hsLang=en-us)
- [![Academy](https://www.bitovi.com/hubfs/limbo/icons/icon-academy.svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy/?hsLang=en-us)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse our tools or contribute to our open source mission.
  
  ](https://www.bitovi.com/open-source?hsLang=en-us)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

- [ Advanced JavaScript ](https://www.bitovi.com/academy/learn-advanced-javascript.html?hsLang=en-us)
- [ DOM and jQuery ](https://www.bitovi.com/academy/learn-dom-jquery.html?hsLang=en-us)
- [ Debug JavaScript ](https://www.bitovi.com/academy/learn-to-debug-javascript.html?hsLang=en-us)
- [ Learn TypeScript ](https://www.bitovi.com/academy/learn-typescript.html?hsLang=en-us)
- [ Learn React Native ](https://www.bitovi.com/academy/learn-react-native.html?hsLang=en-us)
- [ Learn Web Components ](https://www.bitovi.com/academy/learn-web-components.html?hsLang=en-us) 
    - [ Why Web Components ](https://www.bitovi.com/academy/learn-web-components/why-wc.html?hsLang=en-us)
    - [ Our first component ](https://www.bitovi.com/academy/learn-web-components/first-component.html?hsLang=en-us)
    - [ Writing a template ](https://www.bitovi.com/academy/learn-web-components/templates.html?hsLang=en-us)
    - [ Map View ](https://www.bitovi.com/academy/learn-web-components/map-view.html?hsLang=en-us)
    - [ bus-tracker component ](https://www.bitovi.com/academy/learn-web-components/bus-tracker-component.html?hsLang=en-us)
    - [ Listing routes ](https://www.bitovi.com/academy/learn-web-components/listing-routes.html?hsLang=en-us)
    - [ Selecting a route ](https://www.bitovi.com/academy/learn-web-components/select-a-route.html?hsLang=en-us)
    - [ Display markers for vehicles ](https://www.bitovi.com/academy/learn-web-components/display-markers.html?hsLang=en-us)
    - [ Customize header styles ](https://www.bitovi.com/academy/learn-web-components/customize-header.html?hsLang=en-us)
    - [ Slotted content ](https://www.bitovi.com/academy/learn-web-components/slotted-content.html?hsLang=en-us)
    - [ Dispatching events ](https://www.bitovi.com/academy/learn-web-components/dispatching-events.html?hsLang=en-us)
- [ Agile Program Management With Jira ](https://www.bitovi.com/academy/learn-agile-program-management-with-jira.html?hsLang=en-us)
- [ Learn RxJS ](https://www.bitovi.com/academy/learn-rxjs.html?hsLang=en-us)
- [ Giving a Training ](https://www.bitovi.com/academy/training.html?hsLang=en-us)
- [ Learn Technology Consulting ](https://www.bitovi.com/academy/learn-technology-consulting.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react.html?hsLang=en-us)
- [ Learn NgRx ](https://www.bitovi.com/academy/learn-ngrx.html?hsLang=en-us)
- [ Learn Graphql ](https://www.bitovi.com/academy/learn-graphql.html?hsLang=en-us)
- [ Learn Docker ](https://www.bitovi.com/academy/learn-docker.html?hsLang=en-us)
- [ Learn CanJS ](https://www.bitovi.com/academy/learn-canjs.html?hsLang=en-us)
- [ Learn Angular ](https://www.bitovi.com/academy/learn-angular.html?hsLang=en-us)
- [ Advanced TypeScript ](https://www.bitovi.com/academy/advanced-typescript.html?hsLang=en-us)

[View More Courses](https://www.bitovi.com/academy/#courses) [ ]

# Dispatching events `page`

Learn how to use custom events to provide information on the *state* of your component to parent elements.

## Overview

In this part we will:

- Handle click events on the map markers.
- Display the bus destination for selected markers.

## Problem

We want to display the bus destination for the selected marker. This should look similar to how the selected route is displayed as an overlay of the map, but float just below the route overlay. When no routes or markers are selected it should be invisible; when it is clicked it should say `Vehicle: DESTINATION`, where destination can be gotten from the vehicle object that is used to construct the marker.

![Selecting a marker displays the vehicle associated with that marker.](https://bitovi.github.io/academy/static/img/web-components/bt-display-vehicle.gif)

## How to Solve This Problem

1. Add the provided CSS to the `bus-tracker` template.
2. Add the `#selected-vehicle` element to the bus-tracker’s `footer`.
3. Add an event listener for when a marker is selected which dispatches an event called `vehicle-selected`.
4. Add an event listener in `bus-tracker` for the `vehicle-selected` event on the `google-map-view`.
5. In the event listener set the `vehicle.des` in the `#selected-vehicle` element.

## Technical requirements

- For displaying the selected vehicle use a `span` element and give it an `id` of `selected-vehicle`. It will have mostly the same styles as the selected route. You could use the same CSS but just modify it to work for both. For simplicity here’s the full CSS needed:
  
  ```
  #selected-vehicle:not(.vehicle-selected) {
    display: none;
  }
  .vehicle-selected {
    line-height: 1;
    position: absolute;
    z-index: 1;
    text-align: right;
    background: rgba(6, 6, 6, 0.6);
    top: 55px;
    font-size: 11px;
    right: 10px;
    padding: 6px 10px;
    color: white;
    border-radius: 2px;
    cursor: pointer;
  }
  .vehicle-selected small {
    display: block;
    font-size: 14px;
    color: #ddd;
  }
  ```
- Markers are not DOM elements and therefore don’t have all of the same methods. To listen to events you use a slightly different mechanism:
  
  ```
  marker.addListener('event-name', () => {
    // Do whatever
  });
  ```
  
  Where `event-name` is the event you’re interested in.
- Use this snippet for setting the the destination of the bus:
  
  ```
  <small>Vehicle:</small> VEHICLE_DESTINATION
  ```

## What you need to know

- Using `CustomEvent` to create new event types.
- Using the `detail` property to pass information about an event.
- How to use `handleEvent` to listen to events in an easy way.
- Using `connectedCallback` and `disconnectedCallback` together.

### Custom events

Custom elements can have events associated with them just like any other element. You can listen to events the same way as well; in our case we’ll use [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener).

```
el.addEventListener('any-event', () => {
  // Handle this event just like you would any built-in.
});
```

To dispatch a **custom event** that contains data pertaining to the event, use the [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) constructor. With `CustomEvent` you can include a `detail` property. This can contain any sort of data about the event you want to provide. In our case we’ll use the **route** object as the detail property.

Events are created using `let ev = new CustomEvent({ detail: {...} })`. This creates an event object. To dispatch it, the element (in our case a custom element) has a `dispatchEvent(ev)` function that takes the new event.

```
let el = document.createElement('div');

el.addEventListener('favorite-food', ev => {
  console.log(ev.detail); // -> "pizza"
});

let ev = new CustomEvent('favorite-food', {
  detail: 'pizza'
});
el.dispatchEvent(ev);
```

Events created this way do not [bubble](https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles) by default. This means that if you want to listen to events you must place the listener directly on the element from which the event is dispatched. You can enable bubbling by providing `{ bubbles: true }` when creating the event.

```
let ev = new CustomEvent('my-event', {
  bubbles: true
});
```

### Setting up and tearing down listeners

Most of the time you want to delay setting up event listeners until `connectedCallback` is called. This is for a few different reasons:

- Most events deal with user interaction and the user can only interact with elements in the page.
- Events can reference elements not contained within the element; for example an event might be placed on the `document`. This creates a memory leak where the element’s memory can never be freed.

Use `connectedCallback` to add event listeners and `disconnectedCallback` to remove them. disconnectedCallback is called when the element is removed from the page.

```
class MyElement extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: 'open' });
    this.shadowRoot.append(document.import(template.content), true);

    this.onButtonClick = () => {
      // Do something when buttons are clicked.
    };
  }

  connectedCallback() {
    this.button = this.shadowRoot.querySelector('button');
    this.button.addEventListener('click', this.onButtonClick);
  }

  disconnectedCallback() {
    this.button.removeEventListener('click', this.onButtonClick);
  }
}
```

### handleEvent

Most people are familiar with using `addEventListener` with a function like so:

```
element.addEventListener('click', callback);
```

In a custom element class you’ll often want to call some method on your class when an event occurs, but that might not do what you expect:

```
class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.append(document.import(template.content), true);

    this.button = this.shadowRoot.querySelector('button');
  }

  connectedCallback() {
    this.button.addEventListener('click', this.handleButtonClick);
  }

  handleButtonClick() {
    // Oh no! `this` is not what you expected.
  }
}
```

In the above example we are listening for when a button gets a `click` event and calling `this.handleButtonClick`. However this doesn’t do what you expect; the `this` value when `handleButtonClick` is called will be the *button*, not your element.

You can fix this by using an arrow function instead (as shown in the previous section):

```
this.button.addEventListener('click', ev => this.handleButtonClick(ev));
```

However this creates a *new function*. To tear this down in `disconnectedCallback` you’ll have to keep a reference to it.

A way to avoid this is to use `handleEvent`. [handleEvent](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#the_event_listener_callback) is a special method you can add to any object (not just elements) that makes that object an [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget). You use it like this:

```
let eventTarget = {
  handleEvent(ev) {
    // My `this` is correct!
  }
};

window.addEventListener('scroll', eventTarget);
```

Instead of passing a **function** to addEventListener you pass an object with a `handleEvent` method. This can work for custom elements too. Our previous example becomes:

```
class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.append(document.import(template.content), true);

    this.button = this.shadowRoot.querySelector('button');
  }

  connectedCallback() {
    this.button.addEventListener('click', this);
  }

  disconnectedCallback() {
    this.button.addEventListener('click', this);
  }

  handleEvent(ev) {
    // Our `this` is correct and we can do whatever we need.
  }
}
```

`handleEvent` is convenient when you only have a few events you’re curious about, but when you have many events it can be difficult to differentiate between the events since they are all on the same method.

## Solution

✏️ Add `<span id="selected-vehicle"></span>` inside of the `footer` element below the button. In the `google-map-view` when creating markers add an event listeners to the markers click.

Using `CustomEvent`, display a `vehicle-selected` event with a `detail` of the vehicle. Now in the `bus-tracker` component, listen to event and set the vehicle destination on the `#selected-vehicle` element.

Click to see the solution

```
<style>
  html,
  body {
    height: 100%;
  }
  body {
    font-family: "Catamaran", sans-serif;
    background-color: #f2f2f2;
    display: flex;
    flex-direction: column;
    margin: 0;
  }
  bus-tracker {
    --header-background: salmon;
    --header-text-transform: capitalize;
  }
  header[slot=header] {
    background: crimson;
    color: wheat;
    text-align: center;
  }
</style>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD7POAQA-i16Vws48h4yRFVGBZzIExOAJI"></script>

<bus-tracker>
  <header slot="header">
    <h1>My Bus Tracker!</h1>
  </header>
</bus-tracker>

<template id="bt-template">
  <style>
    :host {
      display: flex;
      flex-direction: column;
    }

    .top {
      flex-grow: 1;
      overflow-y: auto;
      height: 10%;
      display: flex;
      flex-direction: column;
    }

    footer {
      height: 250px;
      position: relative;
    }
    .gmap {
      width: 100%;
      height: 250px;
      background-color: grey;
    }

    header {
      box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.1);
      background-color: var(--header-background, #313131);
      color: white;
      min-height: 60px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      line-height: 1.2;
    }

    header h1 {
      text-align: center;
      font-size: 18px;
      text-transform: var(--header-text-transform, uppercase);
      letter-spacing: 1px;
      margin: 0;
    }
    #selected-route:not(.route-selected) {
      display: none;
    }
    .route-selected {
      line-height: 1;
      position: absolute;
      z-index: 1;
      text-align: right;
      background: rgba(6, 6, 6, 0.6);
      top: 10px;
      right: 10px;
      padding: 6px 10px;
      color: white;
      border-radius: 2px;
      cursor: pointer;
    }
    .route-selected small {
      display: block;
      font-size: 14px;
      color: #ddd;
    }
    .route-selected .error-message {
      font-size: 14px;
      background-color: #ff5722;
      border-radius: 10px;
      padding: 4px 8px 1px;
      margin-top: 5px;
    }
    #selected-vehicle:not(.vehicle-selected) {
      display: none;
    }
    .vehicle-selected {
      line-height: 1;
      position: absolute;
      z-index: 1;
      text-align: right;
      background: rgba(6, 6, 6, 0.6);
      top: 55px;
      font-size: 11px;
      right: 10px;
      padding: 6px 10px;
      color: white;
      border-radius: 2px;
      cursor: pointer;
    }
    .vehicle-selected small {
      display: block;
      font-size: 14px;
      color: #ddd;
    }
    .routes-list {
      padding: 20px 0;
      margin: 0;
      overflow-y: auto;
    }
    .routes-list li {
      list-style: none;
      cursor: pointer;
      background: white;
      border: 1px solid #dedede;
      margin: 1% 2%;
      border-radius: 25px;
      color: #2196f3;
      width: 41%;
      display: inline-flex;
      font-size: 14px;
      line-height: 1.2;
    }
    .routes-list li:hover {
      border-color: transparent;
      background-color: #008eff;
      color: white;
      box-shadow: 0px 5px 20px 0px rgba(0, 0, 0, 0.2);
    }
    .routes-list li .check {
      display: none;
    }
    .routes-list li.active {
      color: #666;
      background-color: #e8e8e8;
    }
    .routes-list li.active .check {
      display: inline-block;
      margin-left: 5px;
      color: #2cc532;
    }
    .routes-list li.active:hover {
      border-color: #dedede;
      box-shadow: none;
    }
    .routes-list button {
      width: 100%;
      padding: 8px 8px 6px;
      border: none;
      border-radius: 25px;
      background: transparent;
      text-align: left;
      font: inherit;
      color: inherit;
    }
    .route-number {
      display: inline-block;
      border-right: 1px solid #dedede;
      padding-right: 5px;
      margin-right: 5px;
      min-width: 18px;
      text-align: right;
    }
    p {
      text-align: center;
      margin: 0;
      color: #ccc;
      font-size: 14px;
    }
  </style>
  <div class="top">
    <slot name="header">
      <header>
        <h1>Chicago CTA Bus Tracker</h1>
        <p id="loading-routes">Loading routes…</p>
      </header>
    </slot>

    <ul class="routes-list"></ul>
  </div>
  <footer>
    <button id="selected-route" type="button">
    </button>
    <span id="selected-vehicle"></span>

    <google-map-view></google-map-view>
  </footer>
</template>
<template id="error-template">
  <div class="error-message">
    No vehicles available for this route
  </div>
</template>
<template id="gmap-template">
  <style>
    .gmap {
      width: 100%;
      height: 250px;
      background-color: grey;
    }
  </style>
  <div class="gmap"></div>
</template>
<template id="route-template">
  <li>
    <button type="button">
      <span class="route-number"></span>
      <span class="route-name"></span>
      <span class="check">✔</span>
    </button>
  </li>
</template>
<script type="module">
const template = document.querySelector('#gmap-template');

class GoogleMapView extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    let nodes = document.importNode(template.content, true);
    this.shadowRoot.append(nodes);

    this.markers = null;
    this._vehicles = null;
  }

  connectedCallback() {
    let gmap = this.shadowRoot.querySelector('.gmap');
    this.map = new google.maps.Map(gmap, {
      zoom: 10,
      center: {
        lat: 41.881,
        lng: -87.623
      }
    });
  }

  get vehicles() {
    return this._vehicles;
  }

  set vehicles(newVehicles) {
    this._vehicles = newVehicles;
    if (this.markers) {
      for(let marker of this.markers) {
        marker.setMap(null);
      }
      this.markers = null;
    }
    if (newVehicles) {
      this.markers = newVehicles.map(vehicle => {
        let marker = new google.maps.Marker({
          position: {
            lat: parseFloat(vehicle.lat),
            lng: parseFloat(vehicle.lon)
          },
          map: this.map
        });

        marker.addListener('click', () => {
          this.dispatchVehicle(vehicle);
        });

        return marker;
      });
    }
  }

  dispatchVehicle(vehicle) {
    let event = new CustomEvent('vehicle-selected', {
      detail: vehicle
    });
    this.dispatchEvent(event);
  }
}

customElements.define('google-map-view', GoogleMapView);

const apiRoot = "https://cta-bustracker.vercel.app/api/";
const getRoutesEndpoint = apiRoot + "routes";
const getVehiclesEndpoint = apiRoot + "vehicles";

const btTemplate = document.querySelector('#bt-template');
const routeTemplate = document.querySelector('#route-template');
const errorTemplate = document.querySelector('#error-template');

class BusTracker extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });

    let frag = document.importNode(btTemplate.content, true);
    this.shadowRoot.append(frag);

    this.routesList = this.shadowRoot.querySelector('.routes-list');
    this.selectedRouteBtn = this.shadowRoot.querySelector('#selected-route');
    this.googleMapView = this.shadowRoot.querySelector('google-map-view');
    this.selectedVehicle = this.shadowRoot.querySelector('#selected-vehicle');
  }

  connectedCallback() {
    this.getRoutes();

    this.googleMapView.addEventListener('vehicle-selected', this);
  }

  disconnectedCallback() {
    this.googleMapView.removeEventListener('vehicle-selected', this);
  }

  handleEvent(ev) {
    let vehicle = ev.detail;
    this.selectedVehicle.innerHTML = `<small>Vehicle:</small> ${vehicle.des}`;
    this.selectedVehicle.classList.add('vehicle-selected');
  }

  async getRoutes() {
    let response = await fetch(getRoutesEndpoint);
    let data = await response.json();
    let routes = data["bustime-response"].routes;

    for(let route of routes) {
      let frag = document.importNode(routeTemplate.content, true);

      frag.querySelector('.route-number').textContent = route.rt;
      frag.querySelector('.route-name').textContent = route.rtnm;

      frag.querySelector('button').addEventListener('click', ev => {
        this.pickRoute(route, ev.currentTarget.parentNode);
      });

      this.routesList.append(frag);
    }

    this.shadowRoot.querySelector('#loading-routes').remove();
  }

  async getVehicles(route) {
    let response = await fetch(getVehiclesEndpoint + '?rt=' + route.rt);
    let data = await response.json();

    this.selectedRouteBtn.innerHTML = `
      <small>Route ${this.route.rt}:</small> ${this.route.rtnm}
    `;

    if (data['bustime-response'].error) {
      let frag = document.importNode(errorTemplate.content, true);
      this.selectedRouteBtn.append(frag);
      this.googleMapView.vehicles = [];
    } else {
      let vehicles = data['bustime-response'].vehicle;
      this.googleMapView.vehicles = vehicles;
    }

    this.selectedRouteBtn.classList.add('route-selected');
  }

  pickRoute(route, li) {
    this.route = route;
    this.getVehicles(route);

    if(this.activeRoute) {
      this.activeRoute.classList.remove('active');
    }
    this.activeRoute = li;
    this.activeRoute.classList.add('active');
  }
}

customElements.define("bus-tracker", BusTracker);
</script>

```

###### On this page...

###### Get help

If you need [help](https://discord.gg/J7ejFsZnJ4) with an exercise, please reach out to us on [Discord](https://discord.gg/J7ejFsZnJ4)

If you find a bug, please [create an issue](https://github.com/bitovi/academy/issues/new) or email [contact@bitovi.com](mailto:contact@bitovi.com?Subject=Training%20Suggestion)

## Join the Bitovi Newsletter

 Do you want to receive updates about new Bitovi Academy courses, Bitovi open source projects, and dev community news? If so, please provide your email. We won’t spam you or give it away for any reason!  
*By providing your email you agree to receive communications from Bitovi*

![](https://bitovi.github.io/academy/static/img/close.svg)

```json
{
     "@context": "http://schema.org",
     "@type": "Organization",
     "mainEntityOfPage":{
          "@type":"WebPage",
          "@id":"https://www.bitovi.com/academy/learn-web-components/dispatching-events.html",
          "description": "Learn how to use custom events to provide information on the *state* of your component to parent elements. 
"
     },
     "url": "http://bitovi.com",
     "logo": "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
     "image": "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
     "name": "Bitovi Web App Consulting",
     "alternateName": "Bitovi",
      "naics": "541511",
      "description": "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
     "address": {
          "@type": "PostalAddress",
          "streetAddress": "1134 Pine Tree Lane ",
          "addressLocality": "Libertyville",
          "addressRegion": "IL",
          "addressCountry": "United States",
          "postalCode": "60048"
     },
     "areaServed": {
          "@type": "GeoCircle",
          "geoMidpoint": {
               "@type": "GeoCoordinates",
               "latitude": "41.8781",
               "longitude": "87.6298"
          },
          "geoRadius": "5000 km"
     },
     "telephone": "312-620-0386",
     "email": "contact@bitovi.com",
     "sameAs": [
          "https://www.facebook.com/BitoviLLC/",
          "https://twitter.com/bitovi",
          "https://www.linkedin.com/company/bitovi"
     ]
}
```