A new Angular project gives you a rare luxury: no legacy decisions to work around. It also gives you an excellent opportunity to invent problems you do not have yet. Before the first useful screen exists, you can already have a shared library, a state framework and a folder structure that needs a presentation.
I prefer a different starting point. Build one real feature all the way through, including its failures. Let that feature expose the decisions the project actually needs. Here is how I would approach a greenfield Angular application with a .NET API in 2026.
Pick one useful journey
Consider a customer portal where users can view invoices. The first slice is not “set up the frontend”. It is: an authenticated customer can see their invoices, open one and understand what happens if it cannot be loaded.
That gives us something concrete to discuss with a product owner. Which invoices belong to this customer? Which fields should be visible? Can several employees access the same organisation? What happens after their access is removed?
Those questions shape the API and the interface together. An empty table backed by a fake array will not force them into the conversation.
Define failure before the spinner
For this first slice I want distinct states for loading, no invoices, a failed request and a successful result. An empty list after an error tells the user something false. A spinner that never ends tells them nothing.
I also want a direct URL to an invoice to work after a refresh. That small requirement tests routing, deployment configuration, session handling and data loading. It is a useful amount of reality for a first feature.
Start with the framework
I would use a supported Angular release, its CLI and standalone components as the starting point. Keep the generated setup understandable, commit the lockfile and agree on the Node version used locally and in CI. Make the first production build early.
I would not spend the first day replacing every default. A team needs a reason to add another build abstraction or test runner, especially when everybody will have to maintain that decision.
The Angular release policy matters here: supported versions and a routine for updates are more valuable than announcing that the application used the newest version on the day it was created. Budget for staying current.
Organise by the work
I prefer feature folders over one enormous collection of components and services. The Angular style guide also recommends organising around feature areas. A small starting structure could look like this:
src/app/
app.config.ts
app.routes.ts
invoices/
invoices.routes.ts
invoice-list.ts
invoice-detail.ts
invoice-api.ts
invoice.ts
account/
account.routes.ts
account-page.ts
shared/
ui/
loading-state.ts
This is a sketch, not a naming law. The important part is that changing the invoice feature does not require exploring six unrelated top-level folders. Keep something local until sharing it solves a real second use case.
A component used in one feature does not become reusable because we put it in shared. It becomes a dependency that other features can now accidentally rely on.
Give state a home
Not all state belongs in the same place. A filter that should survive sharing a link belongs in the URL. Temporary selection can live in a component. Data needed across several screens may justify a feature service. The API remains responsible for persisted business data.
For local reactive values and derived state, Signals are a good fit. Computed signals let you describe a value derived from other signals without manually keeping a second copy up to date.
import { computed, signal } from '@angular/core';
type Invoice = {
id: string;
status: 'paid' | 'unpaid';
};
const invoices = signal<Invoice[]>([]);
const showUnpaidOnly = signal(false);
const visibleInvoices = computed(() =>
showUnpaidOnly()
? invoices().filter(invoice => invoice.status === 'unpaid')
: invoices()
);
The example deliberately contains no HTTP request or global store. It demonstrates one responsibility: deriving the visible list. If the filter becomes part of navigation, the URL should become its source of truth instead of maintaining two competing values.
Keep RxJS where it helps
I would still use RxJS for asynchronous sequences where its operators express the behaviour clearly: user input, request cancellation and combining streams. Signals and RxJS solve overlapping but different problems; choosing one for local state does not require banning the other.
A search box is a useful example. Debouncing input, ignoring repeated values and switching to the latest request can be a clear observable pipeline. Be careful what cancellation means: stopping an HTTP subscription does not undo a server-side operation that has already happened.
A global state library can be justified by complex shared workflows, debugging needs or established team conventions. It should answer an actual problem. Installing one before there is any state worth discussing is architecture by shopping list.
Agree on the API
With a .NET backend, I want an explicit contract for the first feature. Define identifiers, dates, nullable fields, pagination and error responses. A field called date is not a contract if one developer means a calendar date and another means a UTC timestamp.
ASP.NET Core's OpenAPI support can help describe the API and support generated clients. That reduces duplicated typing, but generated TypeScript types do not validate arbitrary runtime responses. The deployed API and the generated client still need to match.
Keep transport details behind a small feature API layer. A page should not need to know how every HTTP error body is structured. Equally, do not hide meaningful distinctions such as unauthenticated, forbidden and temporarily unavailable behind one generic “something went wrong”.
Decide who is allowed
The browser can decide which controls are useful to display. It cannot be trusted to decide which invoices a user may read. The backend must enforce that rule for each relevant operation.
Write the rule down while designing the endpoint, then test it through the real API. A customer changing an invoice ID must not gain access to another customer's data. I work through that example in Security by design in Angular.
Make the form ordinary
Forms deserve an early decision because they spread through a business application quickly. I would start with a consistent approach that the team can explain, using typed reactive forms where their explicit model fits. Angular's reactive forms documentation is a useful baseline.
Define when validation appears, where server errors go and what happens during submission. Labels, keyboard access and understandable messages are part of the feature. They are not polish reserved for the final sprint.
Disabling a submit button helps prevent accidental repeat clicks. It does not make a business operation safe to repeat. If duplicate requests can cause duplicate charges or orders, the server needs a deliberate strategy too.
A design system starts small
Agree on spacing, typography, focus styling and a few controls the first feature needs. Use semantic HTML wherever possible. Add a component abstraction when it removes meaningful duplication or protects a useful convention.
I do not need a universal form engine to ship a customer settings page. I do need an error message that a user can find and understand.
Put the first slice in CI
From the first feature, CI should produce a production build and run checks that can reject a broken change. I would separate fast feedback from more expensive integration work so that one slow task does not hide an immediate compilation error.
- Compile and check the frontend, including templates and agreed lint rules.
- Test important behaviour in components and feature logic.
- Run API integration tests for data access and authorisation.
- Exercise one complete invoice journey in a browser against a test environment.
- Check dependencies, secrets and source code with tools suited to the repository.
For the invoice list, test the empty state, a failed request and the unpaid filter. For access control, test a second user. A coverage percentage cannot choose those cases for you.
The build should be deployable through the real process. Verify route refreshes, configuration, useful error logging and rollback while the application is still small. These are much easier to reason about before six teams depend on the environment.
Leave room to learn
After the first slice, review what hurt. Was data loading duplicated? Did the API contract keep changing? Was a shared component genuinely useful? Now you have evidence for the next architectural decision.
Write down the important choices briefly: the decision, why it fits and what would make you reconsider it. Future developers need that context more than another diagram of boxes labelled “service”.
A greenfield project is successful when the next useful feature is straightforward to build and safe to change. It is not successful because the first commit contains every pattern the team has ever heard of.
That is the kind of groundwork I like to help teams establish as a freelance Angular and .NET consultant: enough structure to work confidently, with decisions grounded in the product rather than in an imaginary future.
