This episode covers architecture and development patterns: feature modules and modular architecture, shared and core modules, domain-driven design with a scalable folder structure, and clean architecture and separation of concerns.

Small applications are easy to maintain. Problems appear as the application grows: files move around, dependencies get tangled, and teams aren't sure where to put code. This is where deliberate architecture comes in.
Episode 18 covers feature modules and modular architecture, shared and core modules, domain-driven design with a scalable folder structure, and clean architecture and separation of concerns. Good architecture keeps an application manageable at large-team scale.
The first principle of modular architecture: group code by feature, not by file type. Each feature has its own folder containing its components, services, models, and routes.
src/app/
fitur/
auth/
produk/
keranjang/
checkout/
shared/
core/The auth, produk, and checkout folders are each features that can be developed and tested independently. Large features are lazy-loaded through routes, so they're only loaded when needed.
Each feature should only talk to other features through agreed-upon interfaces — not import each other's internal components. If two features need a lot from each other, they're probably one feature, or the shared part should move to shared.
core folder) holds one-time services and configuration: HTTP interceptors, guards, error handlers, and global state.Keep the two separate so dependencies stay light. Shared must not import features; core must not contain feature-specific UI components.
Large or rarely accessed features are lazy-loaded. With standalone components, lazy loading happens at the route level:
{
path: 'checkout',
loadComponent: () =>
import('./fitur/checkout/checkout.component')
.then((m) => m.CheckoutComponent),
}CheckoutComponent and its dependencies are downloaded only when the user navigates to the checkout page. Lazy loading keeps the initial bundle small while technically enforcing boundaries between features.
Domain-driven design (DDD) suggests: code should speak the business domain language, not technical jargon. Models named Order, Invoice, and Customer are clearer than DataTable, ItemList, and Record.
export interface Order {
id: string;
customerId: string;
items: OrderItem[];
total: number;
status: 'draft' | 'paid' | 'shipped' | 'cancelled';
}A status with domain values like paid and shipped lets business rules be expressed directly in the type. Put complex business logic in the domain layer — not inside components.
A feature-based structure combined with DDD forms a pattern that grows without major overhauls: add a new feature by creating a fitur/<name> folder, and each feature brings its own models, services, state, and components. Structure consistency matters more than initial perfection.
Clean architecture separates code into layers: presentation (components), application (use cases and state), and domain (models and business rules). Each layer depends inward — presentation knows about application, application knows about domain, and domain knows nothing outside itself.
A full clean architecture implementation can feel heavy for small applications. Start with the most important principle — separation of concerns:
@Component({
selector: 'app-checkout',
standalone: true,
template: `<button (click)="checkout()" [disabled]="memproses()">
Bayar Sekarang
</button>`,
})
export class CheckoutComponent {
private readonly checkoutService = inject(CheckoutService);
readonly memproses = signal(false);
checkout(): void {
this.checkoutService.proses().subscribe({
next: () => console.log('Pesanan dibuat'),
error: (err) => console.error(err),
});
}
}CheckoutComponent only shows a button and triggers a service — there's no business logic inside it. CheckoutService handles creating the order. This thin component is much easier to test and maintain.
The best architecture is the one the whole team understands and uses consistently. Document the folder structure in a README or AGENTS.md, agree on rules in code review, and evaluate the architecture periodically. The most expensive thing isn't a wrong architectural decision — it's an inconsistent one.
Key takeaways:
loadComponent.In the next episode, episode 19, we'll cover modern tooling and build automation — using the Angular CLI, builders, and custom schematics, putting together a build pipeline with linting and formatting, continuous integration for Angular applications, and reproducible builds and release management.