NgModules: The Building Blocks of Angular
NgModules are a core feature of Angular that provide a mechanism to organize an application into cohesive blocks of functionality.
Every Angular app has at least one module, the root module (typically named AppModule), which enables bootstrapping
of the application.
Real-world analogy: Think of NgModules as departments in a large organization. Each department has specific responsibilities, its own team members (components, directives), resources (services), and relationships with other departments. The organization as a whole (the application) depends on these departments working together, but each can focus on its own area of expertise.
NgModule Metadata
An NgModule is defined by a class decorated with @NgModule(). The decorator accepts a metadata object that describes
how the module should be compiled and instantiated:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent
],
providers: [
HeroService
],
exports: [
HeroDetailComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
Key Properties of NgModule Metadata
declarations
The components, directives, and pipes that belong to this module.
Rule: Each component, directive, or pipe can only be declared in one module.
Example: declarations: [HeroComponent, HeroListComponent]
imports
Other modules whose exported classes are needed by components in this module.
Example: imports: [BrowserModule, FormsModule]
exports
Declarations that should be accessible to other modules that import this module.
Example: exports: [HeroComponent]
providers
Creators of services that this module contributes to the global collection of services.
Example: providers: [HeroService]
bootstrap
The main application view, called the root component. Only the root module should set this property.
Example: bootstrap: [AppComponent]
Types of NgModules
Angular applications typically have several types of modules:
AppModule] --> B[Feature Modules] A --> C[Shared Module] A --> D[Core Module] B --> E[Lazy-Loaded Modules] style A fill:#DD0031,color:white style B fill:#C3002F,color:white style C fill:#C3002F,color:white style D fill:#C3002F,color:white style E fill:#C3002F,color:white
Root Module
The root module is the main entry point of your application. It's typically named AppModule and is defined in the
app.module.ts file. This module bootstraps the application and coordinates other modules.
@NgModule({
imports: [
BrowserModule,
AppRoutingModule,
SharedModule,
CoreModule
],
declarations: [
AppComponent,
NavbarComponent,
FooterComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
Feature Modules
Feature modules organize code related to a specific application feature, keeping the code organized and potentially enabling lazy loading.
@NgModule({
imports: [
CommonModule,
ProductRoutingModule,
SharedModule
],
declarations: [
ProductListComponent,
ProductDetailComponent,
ProductFormComponent
],
providers: [
ProductService
]
})
export class ProductModule { }
Shared Module
A shared module contains components, directives, and pipes that are used across multiple modules, preventing code duplication.
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
HighlightDirective,
TruncatePipe,
FormattedDatePipe,
StarRatingComponent
],
exports: [
CommonModule,
FormsModule,
HighlightDirective,
TruncatePipe,
FormattedDatePipe,
StarRatingComponent
]
})
export class SharedModule { }
Core Module
A core module contains singleton services that are used throughout the application and should only be imported once, usually by the root module.
@NgModule({
imports: [
CommonModule,
HttpClientModule
],
providers: [
AuthService,
LoggingService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
})
export class CoreModule {
// Prevent importing this module more than once
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import it in the AppModule only.');
}
}
}
Routing Module
A specialized module that provides routing configuration for a feature module or the whole application.
const routes: Routes = [
{ path: 'products', component: ProductListComponent },
{ path: 'products/:id', component: ProductDetailComponent },
{ path: 'products/:id/edit', component: ProductFormComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductRoutingModule { }
Lazy Loading Modules
Lazy loading is a design pattern that defers the loading of modules until they are actually needed, improving initial load time and performance.
Benefits of Lazy Loading
- Reduces initial bundle size and load time
- Loads features on demand
- Improves application performance
- Encourages better code organization
Implementing Lazy Loading
// app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// products-routing.module.ts
const routes: Routes = [
{ path: '', component: ProductListComponent },
{ path: ':id', component: ProductDetailComponent },
{ path: ':id/edit', component: ProductFormComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductsRoutingModule { }
Real-world analogy: Lazy loading is like a just-in-time inventory system in a warehouse. Instead of stocking everything upfront (which takes space and resources), items are ordered and delivered only when needed. This optimizes space and reduces initial costs.
Module Guidelines and Best Practices
Following these guidelines will help you create a well-organized, maintainable Angular application:
- Feature Modules: Organize code into feature modules for major application features
- Shared Module: Create a shared module for components, directives, and pipes used across features
- Core Module: Place singleton services in a core module imported only by the root module
- Lazy Loading: Implement lazy loading for features that aren't immediately needed
- Module Boundaries: Export only what other modules need to use
- Avoid Circular Dependencies: Structure modules to prevent circular dependencies
- Single Responsibility: Keep modules focused on a specific feature or concern
- Module Size: Keep modules reasonably sized; if a module grows too large, consider splitting it
Angular Components
Components are the most basic building block of an Angular application. A component controls a patch of screen called a view and consists of three main parts:
Real-world analogy: A component is like a specialized worker in a factory. It has a specific tool (template) for creating a particular part, knows exactly what to do (class) to process that part, and wears an ID badge (metadata) that tells others what their job is and what tools they use.
Component Creation and Structure
Let's examine the structure of an Angular component in detail.
The Component Decorator
The @Component decorator identifies a class as a component and provides metadata about the component.
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
// Component logic goes here
}
Component Metadata Properties
selector
CSS selector that identifies this component in a template. When Angular sees this selector in a template, it creates and inserts an instance of this component.
Example: selector: 'app-hero-detail' or selector: '[app-hero-detail]' (as an attribute)
templateUrl
Path to the HTML template file for this component.
Example: templateUrl: './hero-detail.component.html'
template
Inline HTML template for this component (alternative to templateUrl).
Example: template: '<h2>{{hero.name}} details</h2>'
styleUrls
Array of paths to CSS files for this component.
Example: styleUrls: ['./hero-detail.component.css']
styles
Inline CSS styles for this component (alternative to styleUrls).
Example: styles: ['.hero { font-weight: bold; }']
providers
Array of providers for services that this component requires.
Example: providers: [HeroService]
encapsulation
Style encapsulation strategy for this component.
Example: encapsulation: ViewEncapsulation.Emulated
changeDetection
Change detection strategy for this component.
Example: changeDetection: ChangeDetectionStrategy.OnPush
Creating Components with Angular CLI
The Angular CLI provides commands to generate components automatically, saving time and ensuring consistent structure.
# Generate a basic component
ng generate component hero-detail
# Or the shorthand
ng g c hero-detail
# Generate a component in a specific folder
ng g c features/products/product-list
# Generate a component without tests
ng g c hero-detail --skip-tests
# Generate a component with inline template and styles
ng g c hero-detail --inline-template --inline-style
The generated component typically includes:
- A TypeScript class file (
component-name.component.ts) - An HTML template file (
component-name.component.html) - A CSS style file (
component-name.component.css) - A test specification file (
component-name.component.spec.ts)
Component Lifecycle Hooks
Angular components have a lifecycle that begins when Angular instantiates the component class and renders the component view, and ends when Angular destroys the component instance and removes its rendered template from the DOM.
Angular provides lifecycle hook methods that give you the opportunity to act at specific moments in this lifecycle.
Key Lifecycle Hooks
constructor
Called before any lifecycle hooks. Ideal for dependency injection.
constructor(private heroService: HeroService) {
// Constructor logic (minimal, basic init only)
}
ngOnChanges
Called when an input property changes. Receives a SimpleChanges object with current and previous property values.
ngOnChanges(changes: SimpleChanges) {
for (const propName in changes) {
const change = changes[propName];
const to = JSON.stringify(change.currentValue);
const from = JSON.stringify(change.previousValue);
console.log(`${propName}: changed from ${from} to ${to}`);
}
}
ngOnInit
Called once after the first ngOnChanges. Ideal for initialization logic.
ngOnInit() {
// Initialize component
this.loadData();
}
ngDoCheck
Called during every change detection run. Use for custom change detection.
ngDoCheck() {
// Check for changes that Angular may not detect
if (this.hero.name !== this.oldHeroName) {
this.oldHeroName = this.hero.name;
this.updateChart();
}
}
ngAfterContentInit
Called once after content (ng-content) has been projected into the component.
ngAfterContentInit() {
// Content has been initialized
console.log('Content children:', this.contentChildren);
}
ngAfterContentChecked
Called after every check of the component's content.
ngAfterViewInit
Called once after the component's view (and child views) has been initialized.
ngAfterViewInit() {
// View has been initialized
this.chartElement.nativeElement.width = this.width;
}
ngAfterViewChecked
Called after every check of the component's view (and child views).
ngOnDestroy
Called right before Angular destroys the component. Ideal for cleanup.
ngOnDestroy() {
// Cleanup
this.subscription.unsubscribe();
clearInterval(this.intervalId);
}
Real-world analogy: Lifecycle hooks are like scheduled maintenance checks for a machine. Constructor is the initial assembly, ngOnInit is the first power-on setup, the various checks are like periodic maintenance inspections, and ngOnDestroy is the final shutdown procedure before disassembly.
Component Input and Output
Angular components can communicate with each other through input and output properties, establishing a parent-child relationship.
Input Properties
Input properties allow data to flow from a parent component to a child component.
// hero-detail.component.ts
import { Component, Input } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html'
})
export class HeroDetailComponent {
@Input() hero: Hero;
@Input('master') masterName: string;
}
// Parent component template
<app-hero-detail [hero]="selectedHero" [master]="masterName"></app-hero-detail>
Output Properties
Output properties allow the child component to send data to the parent component through events.
// hero-detail.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html'
})
export class HeroDetailComponent {
@Input() hero: Hero;
@Output() heroSaved = new EventEmitter<Hero>();
@Output('heroDeleted') deleted = new EventEmitter<Hero>();
save() {
this.heroSaved.emit(this.hero);
}
delete() {
this.deleted.emit(this.hero);
}
}
// Parent component template
<app-hero-detail
[hero]="selectedHero"
(heroSaved)="onHeroSaved($event)"
(heroDeleted)="onHeroDeleted($event)">
</app-hero-detail>
// Parent component class
onHeroSaved(hero: Hero) {
console.log('Hero saved:', hero);
this.updateHero(hero);
}
onHeroDeleted(hero: Hero) {
console.log('Hero deleted:', hero);
this.deleteHero(hero);
}
Real-world analogy: Components communication is like workers on an assembly line. Inputs are like materials being passed down the line from one worker to the next. Outputs are like notifications being sent back up the line to inform previous workers about the status or request additional materials.
Component Templates
A component's template defines the component's view with HTML that tells Angular how to render the component.
Template Syntax
Angular enhances standard HTML with additional syntax for data binding, event handling, and more.
<!-- Interpolation -->
<h1>{{title}}</h1>
<p>Hero: {{hero.name}}</p>
<!-- Property binding -->
<img [src]="hero.imageUrl">
<button [disabled]="isDisabled">Save</button>
<!-- Event binding -->
<button (click)="save()">Save</button>
<input (input)="onInput($event)">
<!-- Two-way binding -->
<input [(ngModel)]="hero.name">
<!-- Structural directives -->
<div *ngIf="heroes.length > 0">Heroes found!</div>
<ul>
<li *ngFor="let hero of heroes; let i = index">
{{i + 1}}. {{hero.name}}
</li>
</ul>
<!-- Template reference variables -->
<input #heroName>
<button (click)="addHero(heroName.value)">Add</button>
<!-- Pipes -->
<p>Hero joined on: {{hero.joinedDate | date:'longDate'}}</p>
<p>Power level: {{hero.power | number:'1.1-2'}}</p>
Component Styles
Angular provides several ways to add styles to a component, with built-in style encapsulation.
Style Options
- styleUrls: Array of external CSS files
- styles: Inline styles defined in the component metadata
- Template inline styles: Using a
<style>tag within the template
// External CSS files
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css', './shared-styles.css']
})
// Inline styles in metadata
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styles: [`
.hero {
padding: 10px;
border: 1px solid #ccc;
margin-bottom: 10px;
}
.hero-name {
font-weight: bold;
color: #03a9f4;
}
`]
})
// Inline styles in template
@Component({
selector: 'app-hero-detail',
template: `
<style>
.hero { padding: 10px; border: 1px solid #ccc; }
.hero-name { font-weight: bold; color: #03a9f4; }
</style>
<div class="hero">
<h2 class="hero-name">{{hero.name}}</h2>
<p>{{hero.description}}</p>
</div>
`
})
Style Encapsulation
Angular provides three encapsulation methods to control how styles defined in or imported by a component affect the rest of the application.
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css'],
encapsulation: ViewEncapsulation.Emulated // Default
})
export class HeroDetailComponent { }
ViewEncapsulation.Emulated
The default. Emulates shadow DOM behavior by adding unique attributes to elements and scoping styles to those attributes.
Effect: Styles apply only to the component's view and don't leak to other components.
ViewEncapsulation.None
No encapsulation. Styles are added to the global styles.
Effect: Styles are global and affect any matching elements in the application.
ViewEncapsulation.ShadowDom
Uses the browser's native Shadow DOM API (if supported).
Effect: Styles are completely isolated in the Shadow DOM.
Real-world analogy: Style encapsulation is like painting different rooms in a house. Emulated encapsulation (default) is like using painter's tape to ensure the paint only goes where intended. No encapsulation is like painting without any tape, allowing the paint to potentially affect other areas. Shadow DOM encapsulation is like painting a completely separate building - there's no chance of affecting anything else.
Content Projection with ng-content
Content projection allows you to insert content from a parent component into a child component's template, similar to the concept of "slots" in Vue.js or "children props" in React.
Basic Content Projection
// card.component.ts
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header">
<h2>{{ title }}</h2>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
</div>
`,
styles: [`
.card {
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 20px;
}
.card-header {
background-color: #f5f5f5;
padding: 10px;
border-bottom: 1px solid #ccc;
}
.card-body {
padding: 15px;
}
`]
})
export class CardComponent {
@Input() title: string;
}
// Using the card component in a parent template
<app-card title="User Profile">
<p>Name: {{user.name}}</p>
<p>Email: {{user.email}}</p>
<button (click)="editProfile()">Edit Profile</button>
</app-card>
Multi-Slot Content Projection
You can have multiple projection slots by using the select attribute on ng-content.
// layout.component.ts
@Component({
selector: 'app-layout',
template: `
<div class="layout">
<header class="header">
<ng-content select="[header]"></ng-content>
</header>
<main class="main">
<ng-content></ng-content>
</main>
<footer class="footer">
<ng-content select="[footer]"></ng-content>
</footer>
</div>
`,
styles: [`/* layout styles */`]
})
export class LayoutComponent { }
// Using the layout component
<app-layout>
<div header>
<h1>My Application</h1>
<nav>
<a routerLink="/home">Home</a>
<a routerLink="/about">About</a>
</nav>
</div>
<!-- Default content goes to unnamed ng-content -->
<div class="content">
<h2>Welcome to My App</h2>
<p>This is the main content.</p>
</div>
<div footer>
<p>© 2025 My Company</p>
</div>
</app-layout>
Real-world analogy: Content projection is like a modular furniture system. The child component is like a furniture frame with specific slots. The parent component provides the specific inserts (content) that fit into those slots, allowing for customization while maintaining a consistent structure.
Component Communication Patterns
Angular provides several ways for components to communicate with each other:
@Input] A --> C[Child to Parent
@Output + EventEmitter] A --> D[Via Services
Shared State] A --> E[ViewChild/ContentChild
Direct Reference] style A fill:#DD0031,color:white style B fill:#C3002F,color:white style C fill:#C3002F,color:white style D fill:#C3002F,color:white style E fill:#C3002F,color:white
Parent to Child: Input Properties
We've already seen how to use @Input properties to pass data from parent to child.
Child to Parent: Output Properties and EventEmitter
Similarly, we've covered @Output properties and EventEmitter for child-to-parent communication.
Accessing Child Components Directly: ViewChild
The @ViewChild decorator allows a parent component to get a reference to a child component, directive, or DOM element.
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { CounterComponent } from './counter.component';
@Component({
selector: 'app-parent',
template: `
<h2>Parent Component</h2>
<app-counter #counter></app-counter>
<button (click)="incrementCounter()">Increment from Parent</button>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('counter') counterComponent: CounterComponent;
// Alternative syntax using the component type
// @ViewChild(CounterComponent) counterComponent: CounterComponent;
ngAfterViewInit() {
// Child component is available after view is initialized
console.log('Counter initial value:', this.counterComponent.count);
}
incrementCounter() {
this.counterComponent.increment();
}
}
Communication via Services
Services can be used to share data and functionality between components that don't have a direct parent-child relationship.
// data.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private messageSource = new BehaviorSubject<string>('Default message');
currentMessage = this.messageSource.asObservable();
changeMessage(message: string) {
this.messageSource.next(message);
}
}
// sender.component.ts
@Component({
selector: 'app-sender',
template: `
<h2>Sender Component</h2>
<input [(ngModel)]="message" placeholder="Enter message">
<button (click)="sendMessage()">Send Message</button>
`
})
export class SenderComponent {
message: string;
constructor(private dataService: DataService) { }
sendMessage() {
this.dataService.changeMessage(this.message);
}
}
// receiver.component.ts
@Component({
selector: 'app-receiver',
template: `
<h2>Receiver Component</h2>
<p>Message: {{ message }}</p>
`
})
export class ReceiverComponent implements OnInit {
message: string;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.currentMessage.subscribe(message => {
this.message = message;
});
}
}
Practical Example: Creating a Reusable Component
Let's create a reusable card component with content projection and styling.
// card.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent {
@Input() title: string;
@Input() subtitle: string;
@Input() imgSrc: string;
@Input() imgAlt: string = 'Card image';
@Input() theme: 'primary' | 'secondary' | 'danger' | 'success' = 'primary';
}
// card.component.html
<div class="card" [ngClass]="theme">
<div class="card-header" *ngIf="title || subtitle">
<h3 class="card-title" *ngIf="title">{{ title }}</h3>
<h4 class="card-subtitle" *ngIf="subtitle">{{ subtitle }}</h4>
</div>
<div class="card-img-container" *ngIf="imgSrc">
<img [src]="imgSrc" [alt]="imgAlt" class="card-img">
</div>
<div class="card-body">
<ng-content select="[card-content]"></ng-content>
</div>
<div class="card-footer">
<ng-content select="[card-footer]"></ng-content>
</div>
</div>
// card.component.css
.card {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
background-color: #fff;
transition: box-shadow 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.card-header {
padding: 16px;
border-bottom: 1px solid #eee;
}
.card-title {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.card-subtitle {
margin: 8px 0 0;
font-size: 14px;
color: #666;
}
.card-img-container {
width: 100%;
overflow: hidden;
}
.card-img {
width: 100%;
display: block;
transition: transform 0.3s ease;
}
.card:hover .card-img {
transform: scale(1.05);
}
.card-body {
padding: 16px;
}
.card-footer {
padding: 12px 16px;
border-top: 1px solid #eee;
background-color: #fafafa;
}
/* Themes */
.card.primary .card-header {
background-color: #007bff;
color: white;
}
.card.secondary .card-header {
background-color: #6c757d;
color: white;
}
.card.danger .card-header {
background-color: #dc3545;
color: white;
}
.card.success .card-header {
background-color: #28a745;
color: white;
}
// Using the card component
<app-card
title="Product Feature"
subtitle="Everything you need to know"
imgSrc="assets/feature-image.jpg"
theme="primary">
<div card-content>
<p>This feature provides incredible benefits to users including improved productivity and efficiency.</p>
<ul>
<li>Benefit 1: Faster processing</li>
<li>Benefit 2: Enhanced security</li>
<li>Benefit 3: Intuitive interface</li>
</ul>
</div>
<div card-footer>
<button class="btn btn-primary">Learn More</button>
<button class="btn btn-outline">Contact Sales</button>
</div>
</app-card>
This card component is:
- Reusable: Can be used in multiple places with different content
- Configurable: Has several input properties for customization
- Flexible: Uses content projection to allow custom content
- Styled: Has default styling with theme variations
- Interactive: Includes hover effects for better UX
Activities for Practice
Exercise 1: Feature Module Creation
Create a feature module for a "Products" section of an e-commerce application:
- Create a ProductModule with appropriate imports and exports
- Create a ProductRoutingModule with routes for listing, details, and editing products
- Create the necessary components (list, detail, form)
- Create a ProductService for handling product data
- Set up lazy loading for the ProductModule from the AppModule
Test your module structure by importing only what's necessary and ensuring the components work correctly.
Exercise 2: Component Communication
Build a parent-child component relationship:
- Create a parent component (TodoListComponent) that manages a list of todos
- Create a child component (TodoItemComponent) that displays and allows editing of a single todo
- Pass todo data from parent to child using @Input
- Emit events from child to parent for todo updates, completion, and deletion
- Implement a service for shared state management
- Add the ability for the parent to directly call methods on the child using @ViewChild
Exercise 3: Reusable Component with Content Projection
Create a reusable Accordion component:
- Create an AccordionComponent that can have multiple panels
- Create an AccordionPanelComponent that contains a header and expandable content
- Use content projection to allow custom content in both the header and body of each panel
- Implement the ability to open/close panels
- Add animations for smooth opening and closing
- Make it configurable with options for allowing multiple open panels, default open panel, etc.
Use this component to create a FAQ section for a website.
Additional Resources
- Angular Documentation: NgModules
- Angular Documentation: Lazy Loading Feature Modules
- Angular Documentation: Introduction to Components
- Angular Documentation: Lifecycle Hooks
- Angular Documentation: Input and Output Properties
- Angular Documentation: Content Projection
- Angular Documentation: View Encapsulation
- Angular University: Understanding Content Projection