# The Angular Service Scoping Mistake That's Killing Your App Performance (And How to Fix It)

**Quick question:** How many times have you added `providedIn: 'root'` to an Angular service without thinking twice?

If you're like most developers (myself included for way too long), the answer is probably "every single time." But here's the thing — that innocent little line might be quietly bloating your app and creating memory leaks you didn't even know existed.

**By the end of this article, you'll know:**

* Why root-level services aren't always the best choice
    
* When and how to scope services properly
    
* Practical examples with real code you can use today
    
* How to write unit tests for scoped services
    
* Pro tips that'll make your Angular apps leaner and faster
    

💬 **Before we dive in, tell me:** Have you ever wondered why your Angular app feels sluggish even with lazy loading? Drop a comment — I bet service scoping is part of the puzzle!

---

## The Root Problem (Pun Intended) 🤔

Let's start with a scenario that probably sounds familiar. You're building an e-commerce app, and you create a `ProductService`:

```tsx
@Injectable({
  providedIn: 'root'  // 👈 The automatic choice
})
export class ProductService {
  private products: Product[] = [];

  constructor(private http: HttpClient) {
    // Load all products immediately
    this.loadProducts();
  }

  private loadProducts() {
    this.http.get<Product[]>('/api/products').subscribe(
      products => this.products = products
    );
  }

  getProducts(): Product[] {
    return this.products;
  }
}

```

Looks innocent, right? But here's what's happening under the hood:

1. **Your service loads ALL products** the moment your app starts
    
2. **It stays in memory** even when users are on completely unrelated pages
    
3. **It's creating unnecessary HTTP requests** before users even need the data
    

**Question for you:** How many of your services are doing exactly this? 🤯

---

## The Smarter Approach: Strategic Service Scoping 🎯

Instead of making everything root-level, let's be intentional about where our services live. Here are the three main scoping strategies:

### 1\. Component-Level Scoping

Perfect for services that should have a fresh instance per component:

```tsx
@Injectable()
export class ShoppingCartService {
  private items: CartItem[] = [];

  addItem(item: CartItem) {
    this.items.push(item);
  }

  getItems(): CartItem[] {
    return [...this.items];
  }

  clearCart() {
    this.items = [];
  }
}

// In your component
@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  providers: [ShoppingCartService]  // 👈 Scoped to this component
})
export class ProductListComponent {
  constructor(private cartService: ShoppingCartService) {}
}

```

### 2\. Module-Level Scoping

Great for services that should be shared within a feature module:

```tsx
@Injectable()
export class UserProfileService {
  private userProfile: UserProfile | null = null;

  constructor(private http: HttpClient) {}

  loadUserProfile(userId: string): Observable<UserProfile> {
    if (!this.userProfile) {
      return this.http.get<UserProfile>(`/api/users/${userId}`).pipe(
        tap(profile => this.userProfile = profile)
      );
    }
    return of(this.userProfile);
  }
}

// In your feature module
@NgModule({
  providers: [UserProfileService],  // 👈 Scoped to this module
  // ... other module config
})
export class UserModule {}

```

### 3\. Smart Root-Level Services

Only use root-level for truly global services:

```tsx
@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  private currentUser$ = new BehaviorSubject<User | null>(null);

  constructor(private http: HttpClient) {
    // Only load auth state - truly global concern
    this.loadStoredAuthState();
  }

  // Methods that are needed app-wide
  getCurrentUser(): Observable<User | null> {
    return this.currentUser$.asObservable();
  }
}

```

💡 **Pro tip:** Ask yourself: "Does every single page of my app need this service?" If not, don't make it root-level!

---

## Real-World Example: E-Commerce Product Categories 🛍️

Let's build a practical example. Imagine you have different product categories, and each category has its own complex filtering logic:

```tsx
// ❌ Bad: Root-level service loading everything
@Injectable({
  providedIn: 'root'
})
export class BadProductService {
  private electronicsProducts: Product[] = [];
  private clothingProducts: Product[] = [];
  private booksProducts: Product[] = [];

  constructor(private http: HttpClient) {
    // Loading everything immediately - wasteful!
    this.loadAllCategories();
  }

  private loadAllCategories() {
    // This runs even if user only visits electronics
    this.http.get<Product[]>('/api/electronics').subscribe(/*...*/);
    this.http.get<Product[]>('/api/clothing').subscribe(/*...*/);
    this.http.get<Product[]>('/api/books').subscribe(/*...*/);
  }
}

```

```tsx
// ✅ Good: Category-specific services
@Injectable()
export class ElectronicsService {
  private products: Product[] = [];
  private filters: ElectronicsFilter = {};

  constructor(private http: HttpClient) {}

  loadProducts(): Observable<Product[]> {
    if (this.products.length === 0) {
      return this.http.get<Product[]>('/api/electronics').pipe(
        tap(products => this.products = products)
      );
    }
    return of(this.products);
  }

  applyFilters(filters: ElectronicsFilter): Product[] {
    // Category-specific filtering logic
    return this.products.filter(product => {
      return this.matchesElectronicsFilters(product, filters);
    });
  }

  private matchesElectronicsFilters(product: Product, filters: ElectronicsFilter): boolean {
    // Complex electronics-specific filtering
    return true; // Simplified for demo
  }
}

// Provide it only in the Electronics module
@NgModule({
  providers: [ElectronicsService],
  // ... other config
})
export class ElectronicsModule {}

```

**Think about it:** Why should your app load clothing filters when the user is browsing electronics? 🤷‍♂️

---

## Unit Testing Scoped Services 🧪

Here's how to properly test your scoped services:

```tsx
describe('ElectronicsService', () => {
  let service: ElectronicsService;
  let httpMock: jasmine.SpyObj<HttpClient>;

  beforeEach(() => {
    const httpSpy = jasmine.createSpyObj('HttpClient', ['get']);

    TestBed.configureTestingModule({
      providers: [
        ElectronicsService,
        { provide: HttpClient, useValue: httpSpy }
      ]
    });

    service = TestBed.inject(ElectronicsService);
    httpMock = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
  });

  it('should load products only once', fakeAsync(() => {
    const mockProducts = [
      { id: 1, name: 'iPhone', category: 'electronics' },
      { id: 2, name: 'MacBook', category: 'electronics' }
    ];

    httpMock.get.and.returnValue(of(mockProducts));

    // First call
    service.loadProducts().subscribe();
    tick();

    // Second call
    service.loadProducts().subscribe();
    tick();

    // HTTP should be called only once
    expect(httpMock.get).toHaveBeenCalledTimes(1);
  }));

  it('should apply filters correctly', () => {
    // Test your filtering logic
    const filters: ElectronicsFilter = { brand: 'Apple', maxPrice: 1000 };
    const results = service.applyFilters(filters);

    expect(results).toBeDefined();
    // Add more specific assertions based on your filter logic
  });
});

```

**Quick question:** How do you usually test your services? Are you testing the scoping behavior too? 💭

---

## Pro Tips That'll Make You Stand Out 🔥

### 1\. Lazy Service Loading Pattern

```tsx
@Injectable()
export class LazyDataService {
  private data$ = new BehaviorSubject<any[]>([]);

  constructor(private http: HttpClient) {}

  getData(): Observable<any[]> {
    if (this.data$.getValue().length === 0) {
      this.http.get<any[]>('/api/data').subscribe(
        data => this.data$.next(data)
      );
    }
    return this.data$.asObservable();
  }
}

```

### 2\. Service Cleanup Pattern

```tsx
@Injectable()
export class CleanupService implements OnDestroy {
  private destroy$ = new Subject<void>();

  constructor(private http: HttpClient) {}

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  getData(): Observable<any> {
    return this.http.get('/api/data').pipe(
      takeUntil(this.destroy$)
    );
  }
}

```

### 3\. Service Factory Pattern

```tsx
export function createCategoryService(category: string) {
  return new CategoryService(category);
}

@NgModule({
  providers: [
    {
      provide: CategoryService,
      useFactory: createCategoryService,
      deps: ['categoryName']
    }
  ]
})
export class DynamicCategoryModule {}

```

👏 **If these patterns are new to you, give this article a clap so others can discover these techniques too!**

---

## Bonus Tips for Angular Performance 💨

1. **Use OnPush Change Detection** with scoped services for better performance
    
2. **Implement proper service disposal** in your modules
    
3. **Consider using Angular's tree-shakable providers** for truly optional services
    
4. **Monitor your bundle size** — scoped services help keep chunks smaller
    

**Your turn:** What's one performance tip you wish you'd known earlier? Share it in the comments! 👇

---

## Recap: Your Service Scoping Cheat Sheet 📋

**✅ Use Root-Level Services For:**

* Authentication and authorization
    
* Global configuration
    
* App-wide state management
    
* Core utilities (logging, error handling)
    

**✅ Use Module-Level Services For:**

* Feature-specific business logic
    
* Data services for specific domains
    
* Services shared within a feature module
    

**✅ Use Component-Level Services For:**

* Temporary state management
    
* Component-specific calculations
    
* Services that need fresh instances
    

**❌ Avoid Root-Level Services For:**

* Heavy data loading services
    
* Feature-specific utilities
    
* Services that aren't needed app-wide
    

---

## What's Next? 🚀

Now that you know how to scope your services properly, your Angular apps will be:

* **Faster** at startup (no unnecessary service initialization)
    
* **Smaller** in memory footprint
    
* **More maintainable** with clear service boundaries
    
* **Better performing** with lazy loading
    

**Action points for you:**

1. Audit your current services — how many are unnecessarily root-level?
    
2. Refactor one service this week using the patterns above
    
3. Add proper unit tests for your scoped services
    
4. Monitor your app's performance before and after
    

---

## Let's Connect! 🤝

**💬 What did you think?** Have you been over-using root-level services too? Or do you have a different approach to service scoping? I'd love to hear your experience in the comments!

**👏 Found this helpful?** If this article saved you from a performance headache, smash that clap button (or give it 5 claps!) so other developers can find it too.

**📬 Want more Angular tips like this?** I share practical Angular insights every week. Follow me here on Medium or connect with me on LinkedIn \[@sadhana\_dev\] for more developer tips that actually work.

**🔥 What should I write about next?** Vote in the comments:

* A) Advanced Angular Testing Patterns
    
* B) Angular Performance Optimization Deep Dive
    
* C) Building Scalable Angular Architecture
    

Let's build better Angular apps together! 🚀

---

### 🚀 Follow Me for More Angular & Frontend Goodness:

I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.

* 💼 [**LinkedIn**](https://www.linkedin.com/in/errajatmalik/) — Let’s connect professionally
    
* 🎥 [**Threads**](https://www.threads.net/@er.rajatmalik) — Short-form frontend insights
    
* 🐦 [**X (Twitter)**](https://twitter.com/er_rajatmalik) — Developer banter + code snippets
    
* 👥 [**BlueSky**](http://devrajat.bsky.social/) — Stay up to date on frontend trends
    
* 🌟 [**GitHub Projects**](https://github.com/malikrajat) — Explore code in action
    
* 🌐 [**Website**](https://malikrajat.github.io/) — Everything in one place
    
* 📚 [**Medium Blog**](https://medium.com/@codewithrajat/) — Long-form content and deep-dives
    
* 💬 [**Dev Blog**](https://dev.to/codewithrajat) — Free Long-form content and deep-dives
    
* ✉️ [**Substack**](https://codewithrajat.substack.com/) — Weekly frontend stories & curated resources
    
* 🧩 [**Portfolio**](https://rajatmalik.dev/) — Projects, talks, and recognitions
    
* ✍️ [**Hashnode**](https://hashnode.com/@codeswithrajat) — Developer blog posts & tech discussions
    

---

### 🎉 If you found this article valuable:

* Leave a **👏 Clap**
    
* Drop a **💬 Comment**
    
* Hit **🔔 Follow** for more weekly frontend insights
    

Let’s build cleaner, faster, and smarter web apps — **together**.

**Stay tuned for more Angular tips, patterns, and performance tricks!** 🧪🧠

[✨ Share Your Thoughts To 📣 Set Your Notification Preference](https://forms.gle/mdfXEVh3AxYwAsKw5)
