Angular provider scopes explained

Reading Time: 8 minutes

Services are one of the building blocks of Angular you will see in every Angular application. Their main purpose is to increase modularity and reusability or in other words to separate a component’s view-related logic from any other kind of processing. Usually, components are delegating various tasks to services like fetching data from a server, validating user input, logging, etc. By defining these kinds of tasks in injectable services, we are making them available to any component.

One service is just a TypeScript class that has @Injectable decorator attached. This decorator makes service available to Angular’s Dependency Injection (DI) mechanism which is built into the Angular framework.

import { Injectable } from '@angular/core';

@Injectable()
export class ExampleService {

  constructor() { }
}

When components like consumers reference one service, Angular DI provides an instance from that service.

import { Component } from '@angular/core';
import {ExampleService} from "./services/example.service";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  // Reference to ExampleService
  constructor(private exampleService: ExampleService) {
  }
}
Source: https://memegenerator.net/instance/55044567/futurama-fry-wait-a-second

How the Angular DI knows how many instances of one service to provide? Is it always just one? How can we tell Angular how many instances to create?

The answer is: provider scopes.

Before we dive into provider scopes and ways of providing service in Angular, let’s define module injector (ModuleInjector) hierarchies.

Visualization of module injector hierarchy

On the image above we can notice the ModuleInjector hierarchy:

  • Platform Module injector – the top module injector, usually used for special things like DomSanitizer
  • Root Module injector – main injector and it is the place for all eagerly loaded module providers
  • Lazy Modules injectors – all lazy-loaded modules are creating separate child injector from the root injector

These module injectors are used from Angular Dependency Injection during creating instances of services.

Now it’s time to dive into provider scopes and how everything works in practice.

There are five provider scopes in Angular:

  • Module scope
  • Component scope
  • Root scope
  • Platform scope
  • ‘any’ injector

Module scope provider

When the service is registered in providers array in one @NgModule, we say its service in the module.

...
import {ExampleService} from "./services/example.service";

@NgModule({
  ...
  providers: [ExampleService]
})
export class AppModule { }

There are two different scenarios to cover when one service is provided in Angular Module:

  • Service is provided in the root module or in an eagerly loaded module – The Angular DI mechanism will use a Root Module Injector and will create one service instance which will be shared between the root module and eagerly loaded modules (all providers from all imported modules are merged into the root injector)
  • Service is provided in a lazy-loaded module – The Angular DI mechanism will use Lazy Module (child injector) and will create one instance for every different lazy-loaded module where it is provided

Component scope provider

One service can be provided also in @Component, registering should be also made in the providers’ array. This means if the service is provided and referenced in a particular component, then for that component and all their children will be created one separate instance of the service, not depending on other providers. This is applied per instance of the component, which in practice means if that component has 3 instances, 3 instances of the service will be created.

import { Component } from '@angular/core';
import {ExampleService} from "./services/example.service";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [ExampleService]
})
export class AppComponent {
  // Reference to ExampleService
  constructor(private exampleService: ExampleService) {
  }
}

The component scope provider is the most specific and Angular starts searching for providers at the first from component and then goes up by hierarchy until it finds it or to the last Platform Module Injector.

Root scope provider

Starting from Angular version 6 it is introduced the possibility to provide a service without registering in @NgModule or @Component, instead of that the providing info should be placed inside @Injectable decorator with providedIn: ‘root’ option.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ExampleService {

  constructor() { }
}

With this kind of providing services, they are provided in Root Module Injector (typically called AppModule) and Angular DI creates a single, shared instance of the service and injects the same instance in every reference. Actually, the service acts like Singleton (Singleton pattern). This is valid if it is referenced in lazy and non-lazy modules too, they all receive the same instance.

Visualization where services with providedIn: ‘root’ option will be provided

The other benefit of this kind of registering is the tree-shaking option for optimizing the app bundle size, if it turns out that the service is not referenced anywhere, Angular DI does not register the service into the root injector at all.

Platform scope provider

Starting from Angular 9, one of the two new ways of providing services is providedIn: ‘platform’ option.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'platform'
})
export class ExampleService {

  constructor() { }
}

When one service is defined with providedIn: ‘platform’ it means that the service will be provided in Platform Module Injector and it acts like Singleton for all applications, also all lazy modules will use the instance from the platform.

Visualization where services with providedIn: ‘platform’ option will be provided

This kind of provider looks similar to ‘root’, but the key difference is only when we are running multiple Angular applications in the same window. Every application that runs in the window will have a separate Root Module Injector, but they will both share the Platform Module Injector. In practice this means, we are sharing the services over the application boundaries.

‘any’ Injector

This kind of providing services is the second newly added option starting from Angular version 9, the syntax is providedIn: ‘any’.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'any'
})
export class ExampleService {

  constructor() { }
}

Providing a service like this means every service will be provided in every module where it is used. So one service can have more than one instance, depending on its usages. The rule here is that every lazy-loaded module will have its own instance, and all eagerly loaded modules will share one instance provided by the Root Module Injector.

Visualization where services with providedIn: ‘any’ option will be provided

Summary

When Angular finds one service referenced in some component, in order to create an instance or use one of the created ones Angular starts looking for a provider in the following order:

  1. Component providers
  2. Module providers
  3. Root providers
  4. Looks for providedIn: ‘any’ injector and decides the number of instances
  5. Platform providers

Component scope provider option mostly will be used in some edge cases for example when we have dynamically created tabs, all have the same component like root, but every tab should have its own state, then we added one service in providers list of the tab root component and for every tab, we will have a different instance.

Module scope provider option was the default option for providing services before Angular 6, nowadays it is also very much used when we want one instance per lazy-loaded module, but as we exposed previously starting from Angular 9 this can be achieved with providedIn: ‘any’ option.

providedIn: ‘root’ this option is the default option when we create a new injectable service in Angular and most of the time we will need tree-shakable singleton services within an application.

providedIn: ‘any’ this is a very helpful option if we want to make sure that one service is singleton within module boundaries.

providedIn: ‘platform’ this option will be mostly used when we want singleton service within several Angular applications which run in the same window.

Using the Vue3’s composition API in Vue2

Reading Time: 4 minutes

Vue3 is officially out since September 18 and along with it comes the composition API which is replacing the old options API. The new composition is defined as a set of additive, function-based APIs that allow the flexible composition of component logic.

We are introduced to the composition API because with the old options API as the component grows the readability of the components was not quite pleasant or comfortable and was starting to get messy, the code reuse patterns had some drawbacks, the support for TypeScript was limited etc.
The visual comparison of both APIs look something like this:

First, we must install @vue/composition-api via Vue.use() before using other APIs.
Import the VueCompositionApi:

import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'

Register the plugin:

Vue.use(VueCompositionApi)

And then we are ready to start.

So, the new API must contain the setup() function which serves as the entry point for using the composition API inside the components. setup() is called before the beforeCreate hook. The component would look like this:

<template>
    <div> {{ note }} </div>
    <div> {{ data.count }} </div>
</template>

<script>
import { ref, computed, reactive, onMounted } from '@vue/composition-api'

export default {
  setup() {
    const notes = ref([])
    async function getData() {
      notes.value = await DataService.getNotesData()
    }

    const data = reactive({
      count: 0,
      actions: ['firstAction', 'secondAction', 'thirdAction'],
      object: {
        foo: 'bar'
      }
    })
    const computedData = computed(() => notes.value)
    
    onMounted(() => {
      getData()
    }

    return {
      ...toRefs(data),
      notes,
      computedData
    }
  }
}
</script>

In the code above we can see the structure of the new API. We have the setup() function, which is exported in the script tag.
Inside the setup function we can see several familiar properties.

const notes = ref([])

This is initializing a property inside the setup function scope. On this property, we must add ref if we want to make it reactive, which means if we don’t add the ref and we make a change to that variable, the change won’t be reflected in the DOM. This is the same as initializing variable in the data() in Vue2:

data() {
  return {
    notes: []
  }
}

As we can see we do not have the method section for creating functions, instead we define the functions in the setup(). After that, the method is used in the mounted hook which is a bit different than the one in the options API.

Some of the hooks were removed, but almost all of them are available in a similar form.

  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • activated -> onActivated
  • deactivated -> onDeactivated
  • errorCaptured -> onErrorCaptured

In addition, two new debug hooks were added to the composition API:

  • onRenderTracked
  • onRenderTriggered

Computed and watch are still available. In the code above we can see how computed is used to return the notes values.

Reactive() is similar to ref() and if we want to create a reactive object we can still use ref(). But underneath the hood, it’s just calling reactive(). On the other side, reactive() will not work with primitive values. It takes an object and returns a reactive proxy of the original. The big difference is when you want to access data defined using reactive(), for example, if we want to use the count, we must do it like:

<div> {{ data.count }} </div>

One very important thing is to convert a reactive object to plain object is using the toRefs and return the data like in the component:

return {
  ...toRefs(data)
}

This is just a small piece of the cake, only something to begin with using the composition API in Vue2 application. For more, you can visit the documentation on the following link.

Typescript/ES7 Decorators to make Vuex modules a breeze

Reading Time: 5 minutes

Overview

Who does not like to use Vuex in a VueJS App? I think no one πŸ™‚

Today I would like to show you a very useful tool written in TypeScript that can boost your productivity with Vuex: vuex-module-decorators. Lately, it’s getting more popular. Below you can see the weekly downloads of the package which raise up constantly:

Weekly downloads on npmjs.com

But what does it exactly do and which benefit does it provide?

  • Typescript classes with strict type of safety
    Create modules where nothing can go wrong. The type check at compile time ensures that you cannot mutate data that is not part of the module or cannot access unavailable fields.
  • Decorators for declarative code
    Annotate your functions with @Action or @Mutation to automatically turn them into Vuex module methods.
  • Autocomplete for actions and mutations
    The shape of modules is fully typed, so you can access action and mutation functions with type-safety and get autocomplete help.

In short, with this library, you can write Vuex module in this format:

import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators'
import { get } from 'axios'

@Module
export default class Posts extends VuexModule {
    posts: PostEntity[] = [] // initialise empty for now

    get totalComments (): number {
        return posts.filter((post) => {
            // Take those posts that have comments
            return post.comments && post.comments.length
        }).reduce((sum, post) => {
            // Sum all the lengths of comments arrays
            return sum + post.comments.length
        }, 0)
    }

    @Mutation
    updatePosts(posts: PostEntity[]) {
        this.posts = posts
    }

    @Action({commit: 'updatePosts'})
    async function fetchPosts() {
        return get('https://jsonplaceholder.typicode.com/posts')
    }
}

As you can see, thanks to this package, we are able to write a Vuex module by writing a class which provides Mutations, Actions and Getters. Everything in one single file. How cool is that?

Benefits of type-safety

Instead of using the usual way to dispatch and commit…

store.commit('updatePosts', posts)
await store.dispatch('fetchPosts')

…with the getModule Accessor you can now use a more type-safe mechanism that does not offer type safety for the user data and no help for automatic completion in IDEs.

import { getModule } from 'vuex-module-decorators'
import Posts from `~/store/posts.js`

const postsModule = getModule(Posts)

// access posts
const posts = postsModule.posts

// use getters
const commentCount = postsModule.totalComments

// commit mutation
postsModule.updatePosts(newPostsArray)

// dispatch action
await postsModule.fetchPosts()

Core concepts

State

All properties of the class are converted into state props. For example, the following code

import { Module, VuexModule } from 'vuex-module-decorators'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2
}

is equivalent to this:

export default {
  state: {
    wheels: 2
  }
}

Mutations

All functions decorated with @Mutation are converted into Vuex mutations. For example, the following code

import { Module, VuexModule, Mutation } from 'vuex-module-decorators'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2

  @Mutation
  puncture(n: number) {
    this.wheels = this.wheels - n
  }
}

is equivalent to this:

export default {
  state: {
    wheels: 2
  },
  mutations: {
    puncture: (state, payload) => {
      state.wheels = state.wheels - payload
    }
  }
}

Actions

All functions that are decorated with @Action are converted into Vuex actions.

For example this code

import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import { get } from 'request'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2

  @Mutation
  addWheel(n: number) {
    this.wheels = this.wheels + n
  }

  @Action
  async fetchNewWheels(wheelStore: string) {
    const wheels = await get(wheelStore)
    this.context.commit('addWheel', wheels)
  }
}

is equivalent to this:

const request = require(β€˜request')

export default {
  state: {
    wheels: 2
  },
  mutations: {
    addWheel: (state, payload) => {
      state.wheels = state.wheels + payload
    }
  },
  actions: {
    fetchNewWheels: async (context, payload) => {
      const wheels = await request.get(payload)
      context.commit('addWheel', wheels)
    }
  }
}

Advanced concepts

Namespaced Modules

If you intend to use your module in a namespaced way, then you need to specify so in the @Module decorator:

@Module({ namespaced: true, name: 'mm' })
class MyModule extends VuexModule {
  wheels = 2

  @Mutation
  incrWheels(extra: number) {
    this.wheels += extra
  }

  get axles() {
    return this.wheels / 2
  }
}

const store = new Vuex.Store({
  modules: {
    mm: MyModule
  }
})

Registering global actions inside namespaced modules

In order to register actions of namespaced modules globally, you can add a parameter root: true to @Action and @MutationAction decorated methods:

@Module({ namespaced: true, name: 'mm' })
class MyModule extends VuexModule {
  wheels = 2

  @Mutation
  setWheels(wheels: number) {
    this.wheels = wheels
  }
  
  @Action({ root: true, commit: 'setWheels' })
  clear() {
    return 0
  }

  get axles() {
    return this.wheels / 2
  }
}

const store = new Vuex.Store({
  modules: {
    mm: MyModule
  }
})

This way the @Action clear of MyModule will be called by dispatching clear although being in the namespaced module mm. The same thing works for @MutationAction by just passing { root: true } to the decorator-options.

Dynamic Modules

Modules can be registered dynamically simply by passing a few properties into the @Module decorator, but an important part of the process is, we first create the store, and then pass the store to the module:

import store from '@/store'
import {Module, VuexModule} from 'vuex-module-decorators'

@Module({dynamic: true, store, name: 'mm'})
export default class MyModule extends VuexModule {
  /*
  Your module definition as usual
  */
}

Installation

The installation of the package is quite simple and does not require many steps:

Download the package

npm install vuex-module-decorators
# or
yarn add vuex-module-decorators

Vue configuration

// vue.config.js
module.exports = {
  // ... your other options
  transpileDependencies: [
    'vuex-module-decorators'
  ]
}

For more details, you can check the plugin’s official documentation.

Conclusion

I personally think this package can rump up your productivity because it embraces the “modularisation” pattern by making your app more scalable. Another big advantage is the fact you have “type-checking” thankfully to TypeScript. If you have a VueJS TypeScript Application, I strongly recommend you this package.