Skip to content
Kumar Chandrachooda
Microservices

Two Frontends, One API

DShop has an Angular 6 SPA and a Blazor 0.7 WASM app over the same gateway. One has the right architecture and broken plumbing; the other has naive architecture and a working shop. The contrast is the lesson.

By Kumar Chandrachooda 30 Sep 2025 5 min read
Two browser windows sharing a single API pipe between them

DShop, having built its backend twice, also built its frontend twice — and the two frontends make an even sharper pair than the two backends, because they fail in opposite directions. DShop.Web is an Angular 6 single-page app from May 2018, feature-foldered and architecturally tidy, whose actual plumbing is a museum of defects and which was abandoned after three weeks. DShop.Blazor is a client-side Blazor 0.7 WASM app from December 2018, architecturally naive in ways that would be alarming in any other runtime, whose plumbing works and which lived another ten months. The better-architected frontend is the one that doesn't work; the working frontend is the one built on questionable foundations. That inversion is the whole story, and this part draws the comparison before part 12 and part 13 take each apart.

The Angular app: right shape, wrong wiring

Open DShop.Web and the structure is exactly what you'd teach. Features live in folders — products, users — each with its own module and its own routing module. There is a shared BaseApiService that centralises HTTP, a typed AuthService for token storage, model classes per entity. Someone who knew Angular laid this out. The price filter is the tell: it pipes a Subject through debounceTime(400) and a hand-written distinctUntilChanged that compares the price bounds by value, cloning the query per emission so the comparison works on content rather than reference. That is idiomatic rxjs, written by someone fluent.

And almost none of it works when you run it. The shared BaseApiService tries to attach the auth token with httpOptions.headers.append('Authorization', token) — and Angular's HttpHeaders is immutable, so append returns a new object that is thrown away; every “protected” call goes out anonymous. The put and delete methods both issue a GET. The PagedResult computes its page count in a field initialiser before the constructor sets the total, so it is permanently empty. The product model has a descirption typo that the template faithfully binds, so every card renders a blank description. The architecture is a well-built frame around plumbing that leaks at every joint — and because the app was abandoned in three weeks, nobody ever ran the paths that would have exposed the leaks. Part 12 is the full tour.

The Blazor app: wrong shape, working shop

DShop.Blazor is the opposite specimen. It is Blazor 0.7.0 — a preview, before a stable release, before .razor files (the components are .cshtml with @functions), running on the mono interpreter in the browser via dotnet blazor serve. And it does things that would horrify you anywhere else. Its component logic lives in plain classes registered as DI singletons and injected into thin views. Its layout component silently signs you up as a new user on first load. It mutates a shared HttpClient's headers on every request. It sleeps for a second after every write to let eventual consistency catch up.

Every one of those is a real smell — and every one of them works, because client-side WASM in 2018 is single-threaded and single-user. A singleton is per-tab. A shared HttpClient has no races because there is no concurrency. The naive choices are load-bearing on a runtime property, and on that runtime they hold. The result is the frontend that actually completes the job: auth attaches, deletes delete, the buy-loop runs end to end. Part 13 reads the shortcuts and the runtime that rescues them.

The CSS finding is that there is no CSS

One lens comes up empty on both, and the emptiness is the finding. There is no design system to critique because there is barely any styling at all. In the Angular app, every component stylesheet is literally zero bytes:

0  src/app/app.component.css
0  src/app/home.component.css
0  src/app/products/products-list/products-list.component.css
0  src/app/users/sign-in/sign-in.component.css
0  src/app/users/sign-up/sign-up.component.css

The global styles.css is two imports and nothing else:

@import "~bootstrap/dist/css/bootstrap.min.css";
@import "~font-awesome/css/font-awesome.css";

Bootstrap for the grid, Font Awesome for icons — and Font Awesome is imported but never used, a phantom dependency pulling a whole icon font for nothing. The Blazor app is the same story with a different template: the default Blazor purple-gradient starter styling, Bootstrap vendored, and — the detail that never stops being funny — jQuery 3.3.1 pulled from a CDN into a WebAssembly app:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

jQuery, in a WASM app whose entire premise is running C# in the browser, present only so a Toastr interop package has something to call. Neither frontend has a token, a variable, a theme, a spacing scale — anything a design system is made of. When the CSS lens finds two empty stylesheets and a phantom font, the honest write-up is that there was no styling to have an opinion about.

What the pair teaches

Line the two up and every axis inverts:

Angular 6 (DShop.Web) Blazor 0.7 (DShop.Blazor)
Architecture feature modules, shared services singletons, component-as-service
Auth header discarded (immutable bug) attached (works)
Writes PUT/DELETE call GET correct verbs
Real-time none — nor in Blazor none — Task.Delay
Styling 0-byte sheets, phantom font default template, CDN jQuery
Lifespan ~3 weeks, then frozen ~10 months
Verdict right shape, broken naive shape, working

To be fair to both: these are teaching artefacts pinned to specific weeks — the Angular app is a snapshot of “how you structure an Angular SPA” frozen before anyone wired it up, and the Blazor app is an explorer's log from a framework that had no stable guidance to follow, so its author invented patterns that a single-user preview runtime happened to reward. Judged as products, both are unfinished. Judged as lessons, they are a matched pair worth more together than apart.

And together they teach the thing neither teaches alone: architecture and function are independent axes, and a review that only checks one will bless the wrong app. A structural reviewer would pass the Angular app — clean modules, shared services, idiomatic rxjs — and flag the Blazor app's singletons and shared mutable client. A functional reviewer, actually clicking Buy, would find the Angular app can't authenticate and the Blazor app completes a purchase. The tidy one is broken and the sketchy one works, and you cannot tell which is which without both reading the code and running it.

Next, the leaks in detail: the Angular app that forgot its auth header.