Angular vs Svelte

Error handling

SvelteKit's model is different by design. No zone, no DI, no interception layer. Errors are caught at whatever boundary happens to enclose them, and each boundary has its own recovery policy.

What you can't do is catch error and keep the page. There's no configuration where SvelteKit reports the error and leaves the tree standing. Angular's ErrorHandler does that; Svelte decided a component that threw an error shouldn't keep rendering.

Svelte Error handling

This causes a bit of fragmentation in Svelte, especially with the fine tuning of error boundaries in templates. Here are the different levels of error handling in Svelte:

  • starts with boundary (the only error handler that does not throw back a redirect)
  • load functions returned "error" (Svelte built in object)
  • client hook handles client-side errors
  • server hook handles routing, template errors and server-side exceptions
  • create +error.svelte in the right place to catch the error (for logging issues, or reversal ... etc), these files can be placed anywhere in a tree of a route, to catch errors within context of that branch
  • finally error.html itself catches everything else
  • Also, add window event listeners and server event listeners to catch errors the leftovers

In template boundaries:

<!--in your +page.svelte --> <svelte:boundary> normal template content {#snippet failed(error, reset)} oh oh, stay put, do not redirect to +error.svelte this is the only hanlder that stays put {/snippet} </svelte:boundary> 

In load functions:

// +page.ts export const load: PageLoad = async ({params}) => { const order = await GetOrder({id: params.id}); if (!order){ // throw a native svelte error that is cauthgt by +error or error.html error(404, {message: 'Order not found'}); } // ... } 

In client and server error handling hooks

// hooks.client.ts export const handleError: HandleClientError = async (error) => { // catch errors thrown from template rendering that are not caught // this also catches unhandled error in case ssr is off, or server handler did not catch error // this returns +error.svelte if exists, or error.html // error.event has the route and url const _error = (<any>error?.error)?.message || error?.message || error; return { message: _error || 'ERROR', source: error.event?.url.pathname }; }; // hooks.server.ts export const handleError: HandleServerError = async (error) => { // catch errors on server rendering, that are not handled // if this is handled here, client hook does not handle it // returns +error.svelte if found, or error.html // error.event has the request const _error = (<any>error?.error)?.message || error?.message || error; return { message: _error || 'UNKNOWN ERROR', source: error.event?.request?.url }; }; 

The +error.svelte  has access to error from the page object. And error.html through %sveltekit.error% directive.

// +error.svelte import { page } from '$app/state'; // read page.error object if found console.log(page?.status, page.error?.message, page.error?.source); // error.html // no access except to %sveltekit.status%, and %sveltekit.error.message% 

If it reaches the last page, it may look like a severe error, but honestly it's just one error that you forgot to handle properly. Svelte could be a total drama queen!

API errors are caught in their fetch handler, or Axios interceptor, or {#await} in templates. But you need to clear out the error and never throw it back, unless you intend to try catch it locally (in template). In fetch you just check response.ok (Axios interception model can be recreated using Svelte fetch but I am too lazy to reinvent such a wheel!)

Of course, this is assuming you never make an explicit http request in a .svelte file. And that you are not Claude.

// axios http.interceptor.ts export const HttpInterceptor = (httpClient: AxiosInstance) => { httpClient.interceptors.response.use( null, function (error) { // pass a custom axios context to decide whether to throw back or handle const _context = error.response?.config?.errorContext if (_context.throwBack) { return Promise.reject(error); } else { return Promise.resolve(error); } }); } // in axiost.d.ts, delcare the extra context import 'axios'; declare module 'axios' { export interface AxiosRequestConfig { errorContext?: { throwBack?: boolean; }; } } // then pass throwBack to http requests to force a throw back, to catch in calling template await httpClient.post('url', data, { errorContext: { // let template catch it throwBack: true } }); 

Finally, there are two extra locations to fine tune error handling, the +layout.svelte, and in your custom Node server,

// +layout.svelte onMount(() => { window.addEventListener('error', (e) => { console.log(e); }); window.addEventListener('unhandledrejection', (e)=>{ console.log(e); }); }); 

In your Node server:

// index.js before everything process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason); }); process.on('uncaughtException', (err, origin) => { console.error('Unhandled Exception:', err); }); 

Angular error handling

In Angular, here are the ways to handle errors, which looks similar to Svelte. The boundary is a new addition that acts like Svlete boundary.

  • template @defer to catch template errors locally
  • template @boundary (still in preview for version 22)
  • global ErrorHandler implementation (provided on bootstrap for all browser errors and unhandled rejection events)
  • routing errors are added directly to route config, navigation events can also be subscribed to at any level to fine tune error handling per route tree.
  • Left overs on bootstrap level

When using Angular with SSR, Angular automatically adds the unhandledRejection and uncaughtException listeners to the server process. These handlers prevent the server from crashing and instead log captured errors to the console.

Since I have been an Angular developer before I used Svelte, this cost me quite a bunch of server crashes because I didn't know I had to manually add it! You live and you learn.

Template and rendering errors:

// error components can be positioned anywhere, where specific errors can be listered to // in app.routes configuration file const MyAppRoutes: Routes = [ { path: '404', component: NotFoundComponent, }, // ... routes { // catch everything else path: '**', // either redirect to 404 // redirectTo: '/404', // or show component in same url: component: NotFoundComponent, pathMatch: 'full', } }, // aslo subscribe to route events head start to catch and log // provideEnvironmentInitializer(appFactory) const appFactory = () => { const router: Router = inject(Router); router.events .pipe( filter( (e) => e instanceof NavigationEnd ) ).subscribe((event) => { if (event.urlAfterRedirects === '/404') { if (event.url !== '/404') { // log 404 error when 404 is redirected to console.log('404', event.url); } } }); // catch NavigationError // this can also be done with the withNavigationErrorHandler token router.events .pipe(filter((e) => e instanceof NavigationError)) .subscribe((event) => { // log unexepected navigation errors console.log( 'unexpected error in navigation', event.error); // redirect to '/error' }); }; // route provider: const AppRouteProviders = [ provideEnvironmentInitializer(appFactory), provideRouter( // routes array: MyAppRoutes, // catch navigation errors here instead (similar to above event subscriber) withNavigationErrorHandler(errorFactory) ) ]; // Bootstrap error handler, main.ts bootstrapApplication(AppComponent, { providers: [ ...AppRouteProviders, // ask Angular to handle window unhandledrejection and error events // via ErrorHandler provideBrowserGlobalErrorListeners() ] }) // the final left-overs .catch((err) => console.log('the left-overs', err)); 

API errors are caught with the http interceptor, using RxJS catchError operator. See the Http Client context we spoke of last week.

// http interceptor file const SOMETHING = new HttpContextToken<any>(() => {throwBack: false}); // helper function export const getErrorContext = (src: any) => { return new HttpContext().set(SOMETHING, src); }; export const OlInterceptorFn: HttpInterceptorFn = (req: HttpRequest<any>, next: HttpHandlerFn) => { return next(adjustedReq) .pipe( // catch all http errors catchError((e) => { // if context rethrows if (req.context.get(SOMETHING)) { const throwBack = req.context.get(SOMETHING).throwBack; // throw back or clear out return throwBack ? throwError(() => e) : of(null); } // throw back return throwError(() => e); }) ); }; // example http call with throwback, let caller catch it return this.http.post(Config.API.resource.query, _params, { context: getErrorContext({throwBack: true}) }); 

Conclusion: error handling acts differently in both frameworks, it is more fragmented in Svelte and leaves little room for fine tuning. Also there is manual work and definite missed gaps. The error.html final catch is an obscure dead end. In Angular again the boilerplate is larger, but it can all be handled in less files, and in the same folder, even if triggered on different routes, because routing is not file system based. In addition to the out of the box window and server error handlers that -I must admit- made me dumber!

Forms

Svelte does not get in the way of creating forms, it just provides the binding process, which is quite simple and works with their Runes $state perfectly well. Angular recently adopted the same concept where the form is just an interface, and the real work happens on a signal. But when you start working with validation, you realize you need to retrain your Svelte into understanding Web 101. I recreated the form input like I did in Angular, but the difference is obvious. In Svelte you're dealing with the DOM. No protection. In Angular you use their out of the box directives to enhance an existing form related directive.

Form directive and validation

Here is an example in Angular for a form directive, that builds on top of the validator class, provided out of the box.

export class InputDirective implements Validator { readonly validator = input<string>(); // for example: readonly maxLength = input<string>(); // this injection is quite powerful, to target the content of the directive as Angular DOM private el = inject(ElementRef); validate(control: AbstractControl): ValidationErrors | null { // ... for example const maxlength = this.maxlength(); if (maxlength && control.value) { if (Validators.maxLength(maxlength)(control)) { this.errorText.set('Too long'); } } } } 

Then in form, the following will be validated on input change:

<--olinput is the directive selector --> <input type="text" olinput [formControlName]="something" [required]="true" [maxLength]="30" /> 

Read about the full implementation in Taming Angular Forms.

In Svelte however, deciding which validation rule overrides the other, targeting the HTML input inside the component, and requesting the form validation upon demand, were pain points. The simplicity of code created in Svelte is not worth the trouble.

Bindable values

That being said, creating an immediate state and binding it to any input form, is much easier in Svelte. If you do not wish to control validation or have a simple form, it is useless boilerplate in Angular.

Updating a form input inside a component in Angular is a nightmare that never gets easier. The easiest way is to deal with the component that has a field, is via input and output signals. In Svelte however, all you need is a bindable property.

<form onsubmit={saveForm} novalidate> <div class="spaced"> <Otp bind:value={formState.code} />  </div>  <button type="submit" class="btn-rev">SAVE</button> </form> 

Where the Otp component simply looks like this, and that's it. The internal input value is reflected to the external formState.code.

<script> let { value = $bindable() }: { value?: any } = $props(); </script> <input type="text" bind:value={value}/> 

This brings us to the concept of directives. In Svelte, you can call a common javascript function anywhere in the template. You can also attach a behavior fairly easily. But when you need to deal with the outside world or have more accurate life cycle hooks for those attachments, there is nothing that can help. Here is an example:

// lib/lazy.ts // example svelte attachment export function lazy(url: string): Attachment { return (element: HTMLImageElement) => { if (!element) return; if (!browser || !IntersectionObserver) { element.src = url; return; } // do some intersection observation ... return () => { // stop watching and destroy }; }; } 

A directive in Angular is similar:

@Directive({ // choose the css selector selector: '[olLazy]' }) // dependecy injection for a safe version of the DOM element constructor( private el: ElementRef, private renderer: Renderer2, ) { } }) export class LazyDirective implements AfterViewInit, OnChanges, OnDestroy { ngAfterViewInit() { // life cycle hook for first time loading } ngOnChanges(c: SimpleChanges) { // watch changes } ngOnDestroy(): void { // destroy } } 

Looks like Angular has much more boilerplate for simple behavior. Wait until you need to detect the server platform:

// inject the actual request from server private request = inject(REQUEST, { optional: true }) as Request; // use it in a directive this.request.get('user-agent') 

In Svelte, there is no way to find out the user agent in the context of an attachment. Go ahead, try, then let me know.

Routing

Angular code driven routing benefits over Svelte choice of 1995 style of file-system based routing, hands down, wins my support. Common guys, WordPress friendly routing must have left some mark on your skin!

Creating logic rules embedded in a file name, that can be renames, deleted, or moved, to make room for more rules, is distasteful.

I am going to stop badgering Svelte(kit) now.

Almost everything that can be done in Angular routing, can somehow be reproduced using file-system based routing. On top of that, Angular also provides a Route class that supports the following:

Reuse strategy

Reuse strategy: allows a custom behavior of the route revisited in sequence, it can be used to store data as user reloads the same route with different parameters, or---the more popular need---destroy the route component and rebuild it.

In Svelte, a #key directive in templates destroys that part of the page when the key changes. But it's limited to one key, and it does not come with other less popular features of the Reuse Strategy. There is also the more vague invalidateAll option for the route function. I used it, and I must admit, I still don't know what to expect.

<!--in svelte component --> {#key id} component content here, reload if id changed in same route {/key} 

In Angular, besides the usual async pipes that observe route changes, a higher order reuse strategy can destroy the whole route and recreate it:

// in route reuse class @Injectable({ providedIn: 'root' }) export class RouteReuseService extends RouteReuseStrategy { // ... shouldReuseRoute(curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot): boolean { // example of forcing a route regeneration by route data // read route data to figure out what to reuse, the pages that are troublesome should not reuse // if data contains "resuse: never" make a check on params values, if changed, return false if (future.routeConfig === curr.routeConfig) { if (future.data && future.data['reuse'] === 'never') { // check all params let reuse = true; curr.data['params'].forEach((n: string) => { // fail if not exact match reuse = reuse && curr.paramMap.get(n) === future.paramMap.get(n); }); return reuse; } } // reuse component? or don't reuse it? return future.routeConfig === curr.routeConfig; } } // provide it at boostrap bootstrapApplication(AppComponent, { providers: [ { provide: RouteReuseStrategy, useClass: RouteReuseService } ] }); // then use in route: { path: ':id', component: OrderDetailsComponent, data: { reuse: 'never', params: ['id'] }, } 

More boilerplate? yes. More control? You bet.

Preloading

In Svelte there is only one data-attribute added in app.html to preload the code, it is not fine tuned, and unpredictable.

In my personal experience, I found out the hard way that the reroute hook runs for every route preload event.

In Angular you can implement a service to choose which routes you need to be preloaded, and optionally fine tune it to preload after a delay.

<!-- sveltekit app.html --> <body data-sveltekit-preload-code="eager"Source:https://dev.to/ayyash/when-simple-is-stupid-3dpl

Related posts