Angular Abyss: Service scope
Dear NgReader, welcome to Angular Abyss series of articles made by me, which describe Angular’s most hot topics and common problems. Hope you enjoy!
All articles:
Problem
We want to understand how to use Angular services in eager and lazy-loaded modules, how DI works and what is service scope.
1. Setup
First of all, let’s create a project using Angular CLI.
ng new my-app --routing
I am using VS Code that has a nice command line shortcut to open editor in current directory.
cd my-app && code .
Now, use angular cli to run following commands.
ng g m modules/a
ng g m modules/b
ng g c components/a
ng g c components/b
ng g s services/shared
You should see such a structure:
First lets modify app-routing.module.ts
And app.component.html to this
We defined our routing. Now modify your service shared.service.ts
Remember that provideIn flag in the class annotation. We will come back to this in a minute.
In a.component.html change template to this:
<p>
A component data: {{data}}
</p>
In b.component.html to this:
<p>
B component data: {{data}}
</p>
Now go to a.component.ts file and write this:
Do exactly the same for b.component.ts
Now run the app and check the result
ng serve -o
If we go to pageA or pageB we will see same result. It displays ‘default’ value from the SharedService. Now its getting interesting..
If we remove privdeIn from SharedService we will get a run-time error from our dependency injection system. Why?
Because provideIn tells Angular to make this service injected into providers in app.module.ts. In our main (root) module. To fix the error. We can simply add our services into providers array like this:
Save the file and see that error is gone. And behavior remains the same ofc.
2. Sharing the service
Lets modify components so they can change the data in service.
Go to a.component.html and b.component.html and append this to both templates.
// other html<button (click)="change()"> Change </button>
Add this function to a.component.ts
Do the same for and b.component.ts but change string to ‘B data’.
Now run the app and check the result! 🌟
As we can see if you change data In pageA and go then to pageB it displays ‘A data’. Why?
Because services are singletons. It means only one instance is created in the whole app.
3. Provide service in modules
Let’s complicate this and NOT provide services in root but in modules.
Remove service from providers in app.module.ts, remove the A and B components from declarations but import A and B modules.
Go to a.module.ts and declare A component and provide service there:
Do the same for b.module.ts
Run the app and see the result.
So? Nothing changed. Okay now go to module b.module.ts and remove the service from providers.
Oops? Still nothing changed. Even that component B is declared in another module it still can use the service provided in module A. Why?
Because no matter in what module you declare a service, if it loaded eagerly, it will always be created as a singleton.
But we can change it to create a scoped services. For that we need a lazy-loaded modules.
But we can make a service private per component. For that we need to declare a service in component class annotation. Like this:
Run the app and see the result. Now BComponent has its own private copy of service. This service is instantiated every time BComponent is created. Keep it in mind when you want it private.
4. Create scoped services via lazy-loading
Let’s make our B module to load lazy. (dont forget to remove SharedService from providers array in BComponent if you did it!)
In directory app/modules/b create a file with name b-routing.module.ts
Now modify the routes in app-routing.module.ts to enable load B lazy.
Add BRoutingModule to imports and exports of BModule. Dont forget to add SharedService to providers.
Now we have SharedService provided in module A which is eagerly loaded when app starts and provided in module B which is lazy loaded.
If you run the app you can see that lazy loaded module B has its own copy of SharedService. Exactly like we make it private for a component, but now for whole module.
Summary: We should remember that services are always singletons in Angular app, if we not make them intentionally scoped.
You can make service scopes by provide it right into component annotation or provide it in a lazy-loaded module. Enjoy!
See the code here.
🚀 If you read something interesting from that article, please like and follow me for more posts. Thank you NgReader! 😏