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} />
</>
);
});
$
dollar sign
The 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.