- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Angular Shop Reactive API Data
You are building 'Globomantics Shop', a lightweight Angular storefront that displays products fetched from a development JSON API. Customers can browse a product list, add items to a cart, and view their cart on a separate page. The application must be zoneless, use standalone components, manage cart state through an injectable service, and proxy API calls through the Angular dev server to avoid CORS errors during development.
Lab Info
Table of Contents
-
Challenge
Step 1: Tour the Globomantics Shop workspace
Globomantics Shop: a zoneless Angular storefront
You are joining the build of Globomantics Shop, a small storefront that lists products, lets a shopper add them to a cart, and shows that cart on its own page. The workspace already has a working shell — nothing renders yet, but the wiring that boots Angular is in place, and every file you will touch already exists with a
// TODOmarking the exact line you own.By the end of this lab the shop will: model products and cart lines with plain TypeScript interfaces, boot with zoneless change detection instead of
zone.js, render a reusable product card built from signal-basedinput()/output(), share cart state through an injectable service, route between a product list and a cart page, and fetch its product catalog from a proxied JSON API withHttpClient. ## The entry point: src/main.ts and src/App.tssrc/main.tsis owned by the toolchain — you will not edit it — and it does exactly one thing: it callsbootstrapApplication(App, appConfig), pulling in the root component fromsrc/App.tsand the provider list fromsrc/app.config.ts. Angular 22 has no@NgModuleand noBrowserModule; a standalone component simply lists what it needs in its ownimports: [], and the root providers — includingprovideZonelessChangeDetection()— live in one config object instead of a module tree.src/App.tscurrently renders a placeholder. You will give it a signal that reports whether the app has finished booting, which is the first piece of observable behaviour you will make it produce. ## Where the rest of the shop will liveAs you move through the steps, you will fill in files already scaffolded under
src/:models/product.tsfor theProductandCartItemshapes,product-card/ProductCard.tsfor the reusable card,cart/CartService.tsfor shared cart state,pages/ProductListPage.tsandpages/CartPage.tsfor the two screens,app.routes.tsfor the router configuration, andservices/ProductService.tsfor the HTTP call that fetches real product data. Each file already compiles — the// TODOcomments mark the single behaviour you add next.info> If you get stuck, you can refer to the provided solution code for each task, available in the
solutionfolder. -
Challenge
Step 2: Model product data and bootstrap a zoneless app
Modelling the data first
Every other part of the shop — the card, the cart, the API call — passes around the same two shapes: a
Productand aCartItem.src/models/product.tsalready declares both interfaces; what is missing is a small factory function,createCartItem, that turns a product and a quantity into a properly-shapedCartItem. Building it now meansCartServicecan reuse it later instead of assembling the object by hand in more than one place. ## Turning on zoneless change detectionAngular's classic change detection patches browser APIs through
zone.jsto know when to re-render. Globomantics Shop skips that entirely:provideZonelessChangeDetection()from@angular/coretells Angular to rely on signals instead, and it is registered once, in the provider listbootstrapApplicationreads fromsrc/app.config.ts. That file already has the shape of the config in place —coreProvidersis the one function you need to fill in. ## Proving the shell bootsWith the providers registered, the last piece of Step 2 is to make the root component itself observable:
src/App.tsholds abootPhasesignal and astatusMessagecomputed from it, but nothing ever movesbootPhaseout of'booting'. That is the job ofmarkReady. -
Challenge
Step 3: Build the reusable ProductCard component
A card that takes its data as an input
ProductCardis the one component every product on the page will render through, so it takes aProductviainput.required<Product>()rather than reaching out for one itself — that keeps it reusable anywhere a product needs showing.src/product-card/ProductCard.tsalready declares that input and aformattedPricecomputed signal meant to turn its rawpricenumber into a display string; you just need to fill in the computation. ## Telling the parent about a clickA card should not know what "adding to cart" means for the rest of the app — it just needs to announce that its button was clicked. That is what the
addToCartoutput, declared withoutput<Product>(), is for: the parent component that renders the card decides what to do with the emitted product. The template already wires the button's click toemitAddToCart; you complete what that method does. ## Noticing when a card mountsngOnInitruns once, right after Angular has resolved a component's inputs, which makes it the right place to do anything that depends on the data already being there — logging which product just rendered, for instance.ProductCarddeclares arenderedsignal for exactly this, still left atfalse. -
Challenge
Step 4: Render the product list and wire cart state to a service
One shared cart, not one per page
Both the product list and the cart page need to see the same cart, so it lives in an injectable,
CartService, rather than inside either page component.src/cart/CartService.tsalready exposes a read-onlyitemssignal and atotalcomputed from it;addItemis the one gap, and it has to replace the array on every change rather than mutate it, since signals only notify readers when they receive a new reference. ## Rendering the list, one card per productProductListPagerenders oneProductCardper entry in itsproductssignal with@for (product of products(); track product.id), and binds each card's(addToCart)output to a single handler. That handler,handleAddToCart, is the only place the page needs to touchCartService— the card itself never has to know the service exists. ## Showing the cart, or saying it is emptyCartPagereads the sameitemsandtotalstraight fromCartService, so any change made on the list page appears here too. Its template uses@if (items().length === 0)to switch between an empty-state message and the list of lines —emptyMessageis the computed signal that decides what that message says. -
Challenge
Step 5: Route pages and fetch products from the proxied API
Giving the shop more than one page
Up to now every page has existed in isolation;
provideRouter()is what turnsProductListPageandCartPageinto destinations a shopper can navigate between. The route table itself lives insrc/app.routes.ts, in a function,buildRoutes, that the exportedroutesconstant below it calls once and hands toprovideRouter. ## Fetching real products over HTTPSo far
productshas started out empty.src/services/ProductService.tsinjectsHttpClient— imported from@angular/common/http, provided by@angular/common— so it can ask the Angular dev-server's proxy for/api/productsinstead of hard-coding data. The workspace already includes that proxy (proxy.conf.mjs) and a small mock catalog API (mock-api.mjs) that starts together with the dev server, so the only code you write is the call itself.loadProductsis the one method that needs to make that call and hand back the resulting observable. ## Wiring the fetch into the pageProductListPage'sngOnInitalready callsloadProductListas soon as the page mounts; the method itself still needs to askProductServicefor data and put whatever comes back into theproductssignal so the@forloop has something real to render. -
Challenge
Step 6: Run the application
Every task is done, and each one was graded the moment you completed it. What you have not done yet is watch the whole application run at once -- which is the only place the pieces you wrote separately become a single working program.
Start the application
Open the Terminal tab and start the dev server:
npm run devIt serves the application on port 4200. Switch to the Web Browser tab to use it: the page is running the functions you implemented in the previous steps, so what you see is your own code rather than a demonstration of it.
The Products page now lists the catalog from the mock API that
npm run devstarts alongside the dev server, so you can add items and open the cart to see the whole shop working.Leave the server running while you look around. It reloads on save, so you can change a file and see the result without restarting anything. ### Run the checks yourself
The lab ran each task's test for you as you went, but they are ordinary tests and you can run them whenever you like. The whole suite:
npx --no-install ng test --no-watch --reporters=dotOr one task at a time:
bash runTest.sh "task 2.1"Each failure names the task it belongs to, so the output tells you which step to go back to rather than which line to stare at. Two things worth trying before you finish: break something on purpose and watch the matching check fail, then put it back. Knowing what a failure looks like is worth as much as knowing what a pass looks like, and it costs you nothing here.
When you are done, everything you wrote is still in the editor tabs -- the lab is yours to keep reading.
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.