Introduction

Angular is a powerful and extensive framework that offers an incredible range of capabilities. But with great power comes a steep learning curve. Mastering Angular often requires digesting a vast amount of material—from core concepts like dependency injection and routing to more advanced topics like reactive programming.

While many of us become comfortable with the core mechanisms—dependency injection, change detection, routing, and so on—let’s be honest: even seasoned Angular developers don’t explore every corner of the framework. There are features that remain hidden, rarely discussed, or misunderstood.

In this article, we’ll explore one of those hidden gems: viewProviders. They may not come up in everyday use, but understanding how they work can give you better control over your component architecture and service scoping. Let’s take a closer look at what makes them special—and where they might be useful in your own projects.

What are viewProviders?

In Angular, providers are like instructions that tell the framework how to create and deliver a given dependency when it’s needed.

When you use providers, the service is available to the component itself, its template, any child components, and even to content projected into it using <ng-content>.

On the other hand, viewProviders limit the service’s visibility strictly to the component’s view. That means it’s accessible only to the component and the elements declared directly in its template—but not to projected content or external child components.

In short: viewProviders allow you to scope a service to a component's view, preventing it from leaking into projected or nested contexts.

This difference is subtle but powerful. It allows you to better encapsulate logic and prevent services from being shared in ways you didn’t intend, especially when working with reusable or content-projection-heavy components.

Difference between providers and viewProviders

This diagram illustrates the difference in service scope between providers and viewProviders. With providers, the service is available to the component, its template, and any projected content (<ng-content>). With viewProviders, the service is only accessible within the component’s own view — projected content is excluded.

Use Case: Card with Dynamic Body

Let’s put theory into practice.

Imagine you’re building a CardComponent that displays a header, some internal logic, and a body whose content can be projected from the outside. The body could include forms, lists, buttons—whatever the consuming component wants.

But you also want the CardComponent to manage some local state, like a CardStateService, which tracks interactions within the card (e.g., expand/collapse, internal toggles). Crucially, you don’t want the projected content to accidentally depend on or modify that internal state.

Here’s what that might look like.

@Component({
  selector: 'app-card',
  template: `
  <article class="card">
    <header class="card-header" (click)="toggleCollapse()">
      <h2>{{ title() }}</h2>
    </header>
    @if (!isCollapsed()) {
      <div class="card-body">
        <ng-content />
      </div>
    }
  </article>
  `,
  styleUrl: './card.scss',
  viewProviders: [CardState]
})
export class Card {
  private readonly state = inject(CardState);

  title = input.required<string>();

  isCollapsed = this.state.isCollapsed;

  toggleCollapse = () => this.state.toggleCollapsed();
}
@Injectable()
export class CardState {
  private readonly _isCollapsed = signal(false);
  isCollapsed = this._isCollapsed.asReadonly();

  toggleCollapsed(): void {
    this._isCollapsed.update((isCollapsed) => !isCollapsed);
  }
}

Now, let's take a look at how the generic card can be used.

<app-card title="Card">
  <app-feature-card-content></app-feature-card-content> <!-- This is projected -->
</app-card>
@Component({
  selector: 'app-feature-card-content',
  templateUrl: './feature-card-content.html',
  styleUrl: './feature-card-content.scss'
})
export class FeatureCardContent {
  // ❌ This will fail with ViewProviders — and that's a good thing
  private readonly state = inject(CardState);

  changeState(): void {
    this.state.toggleCollapsed();
  }
}

In this example:

  • CardState is scoped to Card’s view only.
  • FeatureCardContent, being projected via <ng-content>, does not have access to the state service — even though it's rendered inside the Card.

This gives you a clean separation: internal state remains internal.

So why does this matter?
If we had used providers instead of viewProviders, FeatureCardContent would have been able to inject CardState. That could lead to tight coupling, unpredictable state access, or violations of encapsulation—especially in large, reusable UI libraries.

By using viewProviders, we enforce a boundary, making our component more robust and predictable.

Key Takeaways

  • viewProviders are a specialized Angular feature that limit the scope of a service to a component’s own view — not including projected content (<ng-content>).
  • Use them when you need to encapsulate internal logic, especially in reusable or content-projection-heavy components.
  • They help prevent accidental dependency access from projected children and reduce the risk of service misuse or tight coupling.

In short, viewProviders give you surgical control over where your dependencies live and who can access them. They’re a small feature with a big impact — especially when you care about isolation and clean component APIs.

Outro

Thanks for reading! If you’ve ever had services leak across projected content, or just want more control over DI boundaries, try viewProviders in your next component. Got questions or feedback? Let’s talk below 👇


Tagged in:

Articles

Last Update: July 03, 2025