Client-side Data - Qwik for Angular Developers

Introduction

Hey there! This is the first article out of a series of articles that will help you to get started with Qwik. This series is intended for Angular developers and it makes a bunch of comparisons between patterns of these two frameworks. Today I want to talk about the client side data handling in Qwik with a comparison to Angular. It will focus on the direct relation between parent/child components and data exchange between components which do not have a direct relation.

@Input and @Output in Qwik

When you have got a direct relation between two components, there is the @Input and @Output decorator pattern in Angular.

From parent to child

Let's focus on passing data from the parent component to the child component, first. In Angular, you would use the @Input decorator for a property of the child component. The parent can then pass the data to the child by using the property binding syntax. In Qwik, you can define component properties instead. Imagine the following child component in the beer.tsx

export interface BeerProps {
  name: string;
}

export default component$((props: BeerProps) => {
  return <>{props.name}</>;
});

And then pass the name to the child component as follows

import Beer from './beer';

export default component$(() => {
  return (
    <>
      <Beer name="Öttinger" />
      <Beer name="Paderborner" />
      <Beer name="Hansa" />
    </>
  );
});

From child to parent

The @Output decorator in Angular is used to trigger an event from the child component and listen to it in the parent component. In Qwik, you can define a function property on the child component

import { component$, $ } from '@builder.io/qwik';
import type { PropFunction } from '@bulder.io/qwik';

export interface BeerProps {
  name: string;
  onDrink$: PropFunction<(name: string) => void>;
}

export default component$((props: BeerProps) => {
  return (
    <>
      <h1>{props.name}</h1>
      <button onClick$={$(() => props.onDrink$())}>Drink</button>
    </>
  );
});

And then you can listen to the event in the parent component as follows

import Beer from './beer';

export default component$(() => {
  const onDrinkHandler = $(() => {
    // Do something with the `name` variable
  });

  return (
    <>
      <Beer name="Öttinger" onDrink$={onDrinkHandler} />
      <Beer name="Paderborner" onDrink$={onDrinkHandler} />
      <Beer name="Hansa" onDrink$={onDrinkHandler} />
    </>
  );
});

The $ dollar sign

The $ sign is a special function provided by the Qwik framework. Qwik is designed in a way that it only ships the JavaScript it really needs. With the dollar sign you can tell the compiler, that it extracts the function to its own chunk file.

You can read more about the $ dollar sign in the Qwik documentation.

Shared service in Qwik

In Angular, you can use a shared service to share data between components which do not directly have a relation. It is important where you provide the shared service. With dependency injection you can then inject the same instance of the service (class) in multiple places.

In Qwik, you can define a so called context provider. You can think of a context as a shell which surrounds some of your components. You can then make use of this context within this shell.

import { component$, useStore, useContextProvider } from '@builder.io/qwik';

// Define an interface for the shared state
export interface Stats {
  beersDrunken: number;
}
// Define a context id for the context provider. This context id can be used in the
// useContext hook to get the shared state
export const statsContextId = createContextId<Stats>('stats');

export default component$(() => {
  const store = useStore<Layout>({
    beersDrunken: 0,
  });

  // Here we provide the state to the context provider
  useContextProvider(layoutContextId, store);

  return (
    <>
      <Slot />
    </>
  );
});

In whatever child component within the given context or shell (this means it must be rendered in the component root tags above, e.g. by a content projection with a Slot or directly), you can now "inject" the store using the useContext hook. You have now full access to the shared state and you can read and write to it.

import { component$, useContext } from '@builder.io/qwik';

export default component$(() => {
  // Here we get the shared state from the context provider ("inject" equivalent)
  const store = useContext(statsContextId);
  const onDrinkHandler = $((name) => {
    store.beersDrunken++;
  });

  return (
    <>
      <Beer name="Öttinger" onDrink$={onDrinkHandler} />
      <Beer name="Paderborner" onDrink$={onDrinkHandler} />
      <Beer name="Hansa" onDrink$={onDrinkHandler} />
    </>
  );
});

In a third component you could read the beersDrunken property from the shared state and display it. Use the useContext hook to inject the shared state again. The useStore properties are reactive. This means, that if you change the beersDrunken property, all the elements which rely on this property will be rerendered.

References