Quick answer: Modern Angular architecture is a component tree, not a set of modules. Each component is standalone, declares exactly what it imports, and can be rendered anywhere. Services hold shared logic and are handed to components by dependency injection. Providers registered in app.config.ts are available application-wide.The sections below follow Angular architecture from the bootstrap down: the component tree, services and dependency injection, and the one layering rule that keeps Angular architecture from turning into a tangle.
Table of Contents
Angular architecture: from NgModules to standalone
Older Angular grouped everything into NgModules. A component belonged to exactly one module, and the component’s dependencies were declared in that module’s imports array. That system worked, but as an app grew, a single module file became a shared bottleneck that every feature had to edit.
Standalone components remove the middle layer. A component lists its own imports, so the feature folder holds everything it needs. Angular’s own style guide now recommends standalone components for all new code, and NgModules remain available only for compatibility with existing projects.
import { Component } from '@angular/core';
import { CurrencyPipe } from '@angular/common';
import { ProductCardComponent } from './product-card.component';
@Component({
selector: 'app-product-list',
imports: [CurrencyPipe, ProductCardComponent],
templateUrl: './product-list.component.html',
})
export class ProductListComponent {}
Read the imports array as the component’s dependency list. If the template uses CurrencyPipe, that pipe is imported here — nowhere else. That locality is the single biggest day-to-day change from the module era.
Bootstrapping the application
Instead of a root module, the app starts from a function call. main.ts bootstraps the root component, and app.config.ts holds the providers that apply to the whole application.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
// app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes),
provideHttpClient(),
],
};
Each provide* function switches on one capability: routing, HTTP, the error listeners. That is the modern replacement for importing a module such as HttpClientModule, which is now deprecated.
Services and dependency injection
A service is a plain class that holds logic you want to share — talking to an API, caching data, formatting values. You ask for one with inject() instead of listing it as a constructor parameter.
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ProductService {
private http = inject(HttpClient);
getProducts() {
return this.http.get<Product[]>('/api/products');
}
}
providedIn: 'root' registers the service as a singleton for the whole app, so every component that injects it shares the same instance. That is usually what you want — one cache, one source of truth. For a service that should be fresh per component, provide it in the component’s own providers array instead.
The Angular architecture layering rule to remember
- Components render UI and handle user events. Keep them thin.
- Services hold shared logic and data access. They never touch the DOM.
- Providers decide scope: root for app-wide singletons, component-level for isolated instances.
When you are unsure where code belongs, this rule answers most questions: if it draws on screen it is a component, if it computes or fetches it is a service.
Next in this series: components and their lifecycle hooks, where you meet ngOnInit and friends. If you would rather learn the architecture with a mentor on a real codebase, Ampersand Academy teaches Angular one-to-one.
Frequently asked questions
Do I still need NgModules in Angular?
No, not for new code. Standalone components declare their own imports and are the recommended default. NgModules remain for compatibility with existing applications.
What does providedIn root mean in an Angular service?
It registers the service as a singleton for the whole application, so every component that injects it receives the same instance.
What is the difference between a component and a service?
Components render UI and respond to user events. Services hold shared logic such as data access and never touch the DOM.
How does Angular dependency injection work?
You ask for a dependency with inject() or a constructor parameter, and Angular supplies the registered instance. Where the service is provided decides its scope.
What is app.config.ts in Angular?
It holds the application-wide providers, such as provideRouter and provideHttpClient. It replaces the old root NgModule’s provider list.

