Skip to content

Modern Angular example demonstrating separation of concerns with Google Maps polygon drawing, signal-based reactivity, and form integration using constitutional development principles.

Notifications You must be signed in to change notification settings

Pho3nixHun/angular-maps-architecture-example

Repository files navigation

Angular Google Maps Polygon Drawing Example

A comprehensive example demonstrating separation of concerns in Angular applications using Google Maps with polygon drawing capabilities. This project showcases modern Angular patterns with signal-based reactive programming, proper architectural boundaries, and form integration.

🏗️ Architecture Overview

This application demonstrates clean separation between three distinct responsibilities:

1. GoogleMapComponent - Map State Management

  • Responsibility: Core Google Maps functionality (zoom, center, map instance)
  • Boundaries: Does not know about polygons or form controls
  • Scope: Pure map rendering and basic state management

2. PolygonFormControlComponent - Form Integration

  • Responsibility: Angular Reactive Forms integration
  • Boundaries: Does not know about Google Maps implementation details
  • Scope: Form control value accessor, validation, form state

3. PolygonDrawingDirective - Drawing Behavior

  • Responsibility: Polygon creation, editing, and user interaction
  • Boundaries: Bridges map component and form control without tight coupling
  • Scope: Drawing logic, event handling, coordinate management

🎯 Key Benefits of This Architecture

Clear Separation of Concerns

  • Each component has a single, well-defined responsibility
  • Components can be tested independently
  • Easy to modify or replace individual parts without affecting others

Reusability

  • GoogleMapComponent can be used for any map-based feature
  • PolygonFormControlComponent can work with any drawing implementation
  • PolygonDrawingDirective can be applied to any map component

Maintainability

  • Changes to Google Maps API only affect the map component
  • Form validation logic is isolated in the form control
  • Drawing behavior can be enhanced without touching other components

🏛️ Constitutional Principles Applied

This project follows the Angular Development Constitution principles:

1. Standalone Components Architecture

  • ✅ All components use standalone: true
  • ✅ No NgModules for feature components
  • ✅ Direct imports in component decorators

2. Angular Signals & Reactivity

  • ✅ Signal-based state management: signal<T>()
  • ✅ Signal inputs: input<T>()
  • ✅ Signal outputs: output<T>()
  • ✅ Effects for side effects: effect(() => ...)
  • ✅ Computed signals for derived state

3. TypeScript Namespace Organization

  • ✅ Component-specific types in namespaces: GoogleMapComponent.Coordinates
  • ✅ Clear interface definitions: PolygonDrawingDirective.PolygonChangeEvent
  • ✅ Proper use of utility types: Omit<google.maps.MapOptions, ...>

4. Dependency Injection Patterns

  • ✅ Modern inject() function usage
  • ✅ Host injection: inject(GoogleMapComponent, { host: true })
  • ✅ Injection tokens for configuration: GOOGLE_MAPS_API_KEY

5. Form Integration Best Practices

  • ControlValueAccessor implementation
  • ✅ Signal-based form state management
  • ✅ Proper change detection handling

🔧 Technical Implementation Details

Google Maps Provider Pattern

// Clean provider configuration
provideGoogleMapsApiKey(environment.googleMaps.apiKey),
provideGoogleMapsLoader(),
provideGoogleMapsInitializer(environment.googleMaps.libraries)

Signal-Based Form Control

// Modern signal-based ControlValueAccessor
public value = signal<Coordinates[] | null>(null, { equal: jsonEquals });
public onValueChange = effect(() => {
  queueMicrotask(() => {
    this.onChange(this.value());
  });
});

Host Injection Pattern

// Directive accessing host component
private readonly mapComponent = inject(GoogleMapComponent, { host: true });

🚀 Getting Started

Prerequisites

  • Node.js 18+
  • Angular CLI 20+
  • Google Maps API Key

Installation

npm install

Configuration

  1. Copy .secret.json.example to .secret.json
  2. Add your Google Maps API Key:
{
  "googleMaps": {
    "apiKey": "YOUR_API_KEY_HERE"
  }
}

Development

npm start

📁 Project Structure

src/app/
├── components/
│   ├── google-map/               # Map component (zoom, center, instance)
│   │   ├── google-map.component.ts
│   │   └── google-map.component.scss
│   └── polygon-form-control/     # Form integration component
│       └── polygon-form-control.component.ts
├── directives/
│   └── polygon-drawing/          # Drawing behavior directive
│       └── polygon-drawing.directive.ts
├── providers/
│   └── google-map.provider.ts    # Google Maps configuration
├── environments/
│   ├── environment.ts
│   ├── environment.prod.ts
│   └── environment.model.ts
└── app.ts                        # Main application component

🎨 Component Responsibilities

GoogleMapComponent

// Pure map functionality
public center = input<Coordinates>({ lat: 40.7128, lng: -74.0060 });
public zoom = input<number>(8);
public map = signal<google.maps.Map | null>(null);

PolygonFormControlComponent

// Form integration only
implements ControlValueAccessor {
  value = signal<Coordinates[] | null>(null);
  writeValue(value: Coordinates[] | null): void { /* ... */ }
  registerOnChange(fn: Function): void { /* ... */ }
}

PolygonDrawingDirective

// Drawing behavior bridge
coordinates = input<Coordinates[] | null>(null);
polygonChange = output<PolygonChangeEvent>();
// Bridges map and form without tight coupling

🔄 Data Flow

  1. User draws polygonPolygonDrawingDirective captures interaction
  2. Directive emits eventpolygonChange.emit(coordinates)
  3. Form control updatespolygonControl.setValue(coordinates)
  4. Form reflects changes → Template shows validation state

🛡️ Change Detection Solutions

Problem Solved: ExpressionChangedAfterItHasBeenCheckedError

The application handles Angular change detection challenges with:

// Proper timing for form updates
queueMicrotask(() => {
  this.polygonChange.emit({ coordinates, action: 'edit' });
});

This ensures form state changes happen after change detection completes, preventing timing conflicts.

📋 Features Demonstrated

  • Polygon Drawing: Click to create, drag to edit
  • Form Integration: Reactive forms with validation
  • Real-time Updates: Bidirectional data binding
  • Change Detection: Proper signal-based reactivity
  • Error Handling: Graceful Google Maps API failures
  • TypeScript: Strong typing throughout
  • Responsive Design: Mobile-friendly interface

🔮 Future Enhancements

  • Multiple Polygon Support: Extend to handle polygon arrays
  • Drawing Tools: Add circle, rectangle, and line drawing
  • Persistence: Save/load polygon data from backend
  • Advanced Validation: Custom polygon validation rules
  • Performance: Implement virtual scrolling for large datasets

🧪 Testing Strategy

Unit Testing Approach

  • GoogleMapComponent: Mock Google Maps API, test signal reactivity
  • PolygonFormControlComponent: Test ControlValueAccessor implementation
  • PolygonDrawingDirective: Test host injection and event emission

Integration Testing

  • Form Integration: Test complete drawing → form → validation flow
  • Error Scenarios: Test Google Maps API failures
  • Change Detection: Verify no timing conflicts

📚 Learning Outcomes

This example teaches:

  1. Architectural Patterns: How to separate concerns effectively
  2. Angular Signals: Modern reactive programming patterns
  3. Form Integration: Custom form controls with proper change detection
  4. Google Maps: Professional integration patterns
  5. TypeScript: Advanced typing with namespaces and utility types
  6. Dependency Injection: Modern patterns with inject() function

🏅 Best Practices Demonstrated

  • Single Responsibility: Each component has one clear purpose
  • Composition over Inheritance: Directive composition for behavior
  • Signal-First: Modern Angular reactivity patterns
  • Type Safety: Comprehensive TypeScript usage
  • Clean Code: Self-documenting code with clear naming
  • Performance: Efficient change detection and memory management
  • Maintainability: Easy to extend and modify

🔧 Development Commands

Development server

ng serve

Building

ng build

Running tests

ng test

This project serves as a template for building complex Angular applications with proper architectural boundaries and modern Angular patterns.

About

Modern Angular example demonstrating separation of concerns with Google Maps polygon drawing, signal-based reactivity, and form integration using constitutional development principles.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published