DEV Community

Cover image for Providing in Angular - how to share data
Tomasz Flis
Tomasz Flis

Posted on

Providing in Angular - how to share data

Introduction

In the angular framework, dependency injection, and providers are one of the core features. This feature allows us to use a single data source in many places. For example, we can share one HTTP call result with many components to avoid too many requests for the same data. Another example could be to override one value or a class with another. In this article, I will explain the providers, how to define them, and how to use them in many places.

What are the providers?

Providers in Angular are a source of value that could be shared between many recipients. It could be a static value like, for example, string or boolean, or it could be a whole class that uses the same provider instance. In the Angular role of provider, classes are covered by services. Services are a special kind of class with an Injectable decorator. Thanks to that, we can use other providers inside our class because they can be injected. By default, the Injectable decorator takes as an argument for the providedIn value root, meaning that this current service will be initiated at the root level of the app. More details about that You can find later in this article.

Type of providers

The angular framework has a few types of providers which work differently but, in the end, serve data.

Injection token

One of the possible injectors is the InjectionToken.
It is the simplest provider whose main purpose is to represent values not contained in any class.
An example definition of the injection token is:

export const timezoneToken = new InjectionToken<string>('My timezone');
Enter fullscreen mode Exit fullscreen mode

It is generic and accepts type definition and My timezone as a description. It is useful when we forget about implementing a given injection token and the error contains a given description.

Value provider

The value provider is a provider who accepts the useValue property value that will be set for the provider set in the provide property. It can be any of the values. An example definition can be:

    {
      provide: timezoneToken,
      useValue: 'europe/warsaw'
    },
Enter fullscreen mode Exit fullscreen mode

I used the token from the previous example as a provider and set europe/warsaw as a value.

Factory provider

Sometimes we want to do something with an existing provider and provide it as another new provider instance. We can use a factory provider, which accepts three properties for that purpose. The provide property defines which provider we will provide. Let's make an example. First of all, I will define another injection token:

export const timezoneCityToken = new InjectionToken<string>('My timezone city');
Enter fullscreen mode Exit fullscreen mode

Then to define the factory provider:

    {
      provide: timezoneCityToken,
      useFactory: (timezone: string) => {
          return timezone.split('/')[1];
      },
      deps: [timezoneToken]
    },
Enter fullscreen mode Exit fullscreen mode

In the deps property, we set dependencies for our provider. In this example, I used timezoneToken from the previous example. Those dependencies are passed as params to the method, which is set in the useFactory property. That property accepts a method that returns the value for our new provider.
Sometimes we want to set an array of values under the same provider. To achieve that, We can set property multi to true, so the value of the above provider would be ['warsaw'] instead of just warsaw.

Use class provider

Another type of provider is the useClass provider. It is used when we want to use a standard class to create an instance for our provider. An example of usage can be:

    {
      provide: MyDataAbstarct,
      useClass: UsersList,
    }
Enter fullscreen mode Exit fullscreen mode

And the MyDataAbstarct content is an abstract class, so it cannot have its own instance:

export abstract class MyDataAbstarct {
    abstract itemsList: string[];
}
Enter fullscreen mode Exit fullscreen mode

And the UsersList content:

export class UsersList extends MyDataAbstarct {
    itemsList = ['John', 'Mike', 'Anna'];
}
Enter fullscreen mode Exit fullscreen mode

To reach this list, we can utilize the inject method by:

  readonly list = inject(MyDataAbstarct).itemsList;
Enter fullscreen mode Exit fullscreen mode

So useClass provider is a cool way to have consistency across different places and use common patterns because we can easily replace the used class but keep the provider.

Use existing provider

The last type of provider is the useExisting provider. It works similarly to a useClass, but it shares the same instance with the selected provider. Simple usage can be:

...,
    UsersList,
    {
      provide: MyDataAbstarct,
      useExisting: UsersList,
    },
...
Enter fullscreen mode Exit fullscreen mode

Please remember that useClass would create a separate instance of the selected provider.

Providers usage

Those are examples of which types of providers exists in Angular. Now let's see how we can provide them.

Root level

Services in Angular are classes that can be injected with other providers and are mainly used to serve common logic. We can inject them (to create new instances) at the root level of our application, which is a default approach for services. It is set by a dedicated decorator:

@Injectable({
  providedIn: 'root'
})
Enter fullscreen mode Exit fullscreen mode

Value root means that a given service will be injected at the app's top level. Another value can be platform, meaning we share instances with other apps on the same page. The last possible value is any, meaning that each place will receive a new, unique service instance.

Module level

We can provide any provider at the module level by defining it in providers property, for example:

@NgModule({
  declarations: [
    FlagComponent
  ],
  imports: [
  ],
  providers: [FlagService],
})
export class FlagModule { }
Enter fullscreen mode Exit fullscreen mode

That means FlagService will provide the same instance across all modules and components used in FlagModule.

Component level

Like above, we can use the provider on a component level by adding it to the providers property. The same instance and all children's components will be shared inside that component.

Route level

To decrease imports and lines of code in components, We can set providers on the route configuration, for example:

export const appRoutes: Routes = [
    {
        path: 'flag',
        loadChildren: () => import('./flag/flag.module').then(m => m.FlagModule),
        providers: [
            FlagService,
        ],
    }
]
Enter fullscreen mode Exit fullscreen mode

It works in a similar way as provided on the component level.

To access a provider, we can utilize two ways:

  • inject function in the class body or useFactory provider, for example:
readonly myInstance = inject(MyService);
Enter fullscreen mode Exit fullscreen mode
  • constructor, for example:
constructor(readonly myInstance: MyInstance) {}
Enter fullscreen mode Exit fullscreen mode

Providers direct usage configuration

Sometimes we want to omit a provider's usage, or it can be optional. Angular has dedicated flags for providers to set it up.

  • Optional - marks provider as optional and won't throw an error when not provided but returns null instead,
  • Self - will look for a provider only at the current component or directive level
  • SkipSelf - will skip the current component or directive and look for the provider at the parent level,
  • Host - will look for a provider up to the host level.

To apply those flags with the inject method, we can add them as a configuration object, for example:

  readonly list = inject(MyDataAbstarct, { optional: true })?.itemsList;
Enter fullscreen mode Exit fullscreen mode

Or if we are using a constructor, then we have to add a dedicated decorator:

  constructor(
    @Optional() readonly list: MyDataAbstarct,
  ) {}
Enter fullscreen mode Exit fullscreen mode

Summary

Angular has a powerful mechanism of providing values and shares common sources with many ways of configuring it. Knowing it allows developers to write reusable and clean code. Here You can find more details.

Top comments (0)