I tried Qwik, it’s amazing
But I’ll go back to Angular because of JSX
I’ve worked with Qwik from some times now and I wanted to provide some feedback on my experience.
Some background: I’ve worked with Angular for a while now. I worked with Svelte, Flutter and Next too, but I always came back to Angular. I really thought Qwik was the one though.
Qwik is amazing with a great community and a lot of refreshing features (to name some):
$(() => )
: extract all the content of the in a dedicated file (no third party bundle bleeding out everywhere)server$
: call a function on the backend like if it were on the client.useResource$
: It’s basically astro out of the box.- Speculative Module Fetching: prefetch exactly what the user will need.
I know there would be some price to pay for this greatness:
- Share state should serializable (no third party class) or wrap in a
noSerialize()
function. - Not easy to know before hand if your code is running on the client or the server.
And of course, this is a young framework, so there are a lot of annoying things that would get better some time:
- Hot module reload sometime skip some changes (especially CSS).
- Scoped styles is not easy to manage.
- Performance for production app is not that great on SPA navigation.
I can deal with all of that, as it’ll get better with time. But there is one thing I couldn’t work with : JSX.
Qwik decided to go with JSX, and I understand why, but coming from Angular I’m missing a lot of amazing features I’ve been working with.
The next part explains why I prefer Angular’s system over JSX, and is not specific to Qwik, even if code snippets are taken from Qwik.
Component as HTML Element
In angular every component is an html element. This makes CSS much better because you can select this element in a scoped envrionment :
<mat-toolbar>
<h1>My Page</h1>
</mat-toolbar>
Then in CSS I can do :
mat-toolbar {
justify-content: space-between;
}
To achieve that in Qwik I have to do use a class and pass the scopedId along the way. For every component it gets quickly out of hand.
const { scopedId } = useScopedStyles(styles);
return <Toolbar class={[scopeId, 'toolbar']}>
<h1>My Page</h1>
</Toolbar>
Directive
This is where Angular shines. In JSX you can only create component. So if you want to combine several logic on an element you need to wrap many component around it :
return <Tooltip text="some text">
<Copy content="Some text to copy">
<IconButton>
<Icon name="..."/>
</IconButton>
</Copy>
</Tooltip>
This often leads to a cascade of <div>. And I’m not counting the Wrappers used for CSS because you don’t have access to the underlying HTML Element 😅.
In Angular we have directive which enables combination of behavior on one HTML element :
<button mat-icon-button
cdkCopyToClipboard="Some text to copy"
matTooltip="some text"
>
<mat-icon svgIcon="..."/>
</button>
Access Component
As component are classes, they can expose methods and properties, and you can access :
Parent component with injection dependencies
class FormFieldComponent {
form = inject(FormComponent);
}
Child component with ViewChild
& ContentChild
class FormComponent {
@ViewChild(FormFieldComponent) formField: FormFieldComponent;
}
Typescript & Injection Dependency
Because angular is built on top of Typescript, it’s so easy to navigate in your app with ctrl+click to see the content of a method of service.
With components :
JSX (Qwik):
interface Props {
status: string;
}
export default component$(({
status = 'pending'
}: Props) => {
})
Angular:
export class MyComponent {
@Input() status = 'pending';
}
There is no need for extra work, typescript will infer the types.
With Service
JSX (Qwik) :
interface Service {
property: Signal<string>;
method: QRL<() => string>
}
const ServiceContext = createContextId<Service>('ServiceContext');
export const useServiceProvider = () => {
const property = useSignal('');
const method = $(() => '');
useContextProvider(ServiceContext, { property, method });
}
export const useService = () => useContext(ServiceContext);
const Child = component$(() => {
const service = useService();
useVisibleTask$(() => {
service.method(); // <- Try ctrl+click here
})
return <p></p>
})
export default component$(() => {
useServiceProvider();
return <Child/>
});
If you ctrl+click on the service.method() you’ll end up on the interface, which doesn’t provide much information about how the method is working.
Angular:
@Injectable({ providedIn: 'root' })
class Service {
property = '';
method() {
return '';
}
}
@Component({
selector: 'my-component',
template: '<p></p>'
})
class MyComponent {
service = inject(Service);
constructor() {
this.service.method(); // <- ctrl+click leads to the method definition
}
}
Conclusion
Don’t take me wrong, I loved my experience with Qwik, and it made my live easier on many other thing. It’s just that, as an Angular developer, I took a lot of things for granted while dreaming about all these new features from new frameworks. But once I tried them I realized what I lost.
I hope one day that Qwik will support this kind of great features (especially Directive), but as it’s based on JSX, it might never happen…