Introduction
In this article, we are going to learn how to achieve Separation of Concern in our component or in an application.
Prerequisites
- HTML, CSS, and JS
- Basics of TypeScript.
What do we mean by “Separation of Concern?"
Think of a school, there are different jobs. We have a teachers whose purpose is to teach, students have their own task which is to learn, guards have their own job, the principal has their own job, the library assistant has their separate job etc. Students don’t teach, the lab assistant is not working in place of the guard etc. In object oriented programming we have the same concept.
Our class should perform single operations, the class that does too many things just violates the “Separation of concern” concept and such a class is hard to maintain and hard to test. In the below example the component is involved in two different concerns. One is the presentation logic behind the view and other logic is getting the data. It needs to know the end points and it needs to know the http class. In updating and deleting the data it needs to construct the URL. These are the different concerns. In the larger application we can use the same component in different pages because this may not be the only page which is using the specified component, and if we are using the component in different places then again we need to know the URL and the required endpoints. So any changes in any logic will lead to changes in so many places.
We have a component as below,
- import { Component, OnInit } from '@angular/core';
- import { Http } from '@angular/http';
-
- @Component({
- selector: 'app-posts',
- templateUrl: './posts.component.html',
- styleUrls: ['./posts.component.css']
- })
- export class PostsComponent implements OnInit {
- posts: any[];
- private url = 'http://jsonplaceholder.typicode.com/posts';
- constructor(private http: Http)
- {
- http.get(this.url)
- .subscribe(response=> {
- console.log(response.json());
- this.posts = response.json();
- });
- }
-
- ngOnInit() {
- }
-
- createPost(input: HTMLInputElement)
- {
- let post = {title: input.value};
- input.value='';
- this.http.post(this.url, JSON.stringify(post))
- .subscribe(response => {
- post['id'] = response.json().id;
- this.posts.splice(0,0,post);
- console.log(response.json());
- });
- }
-
- updatePost(post)
- {
- this.http.patch(this.url + '/' + post.id, JSON.stringify({isRead: true}))
- .subscribe(response=> {
- console.log(response.json);
- })
- }
-
- deletePost(post)
- {
- this.http.delete(this.url + '/' + post.id)
- .subscribe(response=> {
- let index = this.posts.indexOf(post);
- this.posts.splice(index, 1);
- console.log(response.json);
- })
- }
-
- }
In our current implementation of PostComponent we have some methods like ngOnInit, createPost, DeletePost etc. There is a problem with this implementation, the problem is that it violates the “Separation of concern” principle.
Also for testing this implementation is little bit harder to test.
Let us suppose we are adding an automated test for the UpdatePost as given below.
- updatePost(post)
- {
- this.http.patch(this.url + '/' + post.id, JSON.stringify({isRead: true}))
- .subscribe(response=> {
- console.log(response.json);
- })
In our unit test we don’t want the live server running. So we want to isolate the component and only focus on the logic implementation here. In other words we don’t want http call to the server because this has two problems; the first problem is if the server is not running then our unit test will fail and the second problem is that all these http calls are going to slow down the our automated test. We want hundred or thousands of unit test to run in a few seconds, that’s why we never ever call the http server in our automated test.
Solution
Solution is to create a separate class that we call a service and this class will only be responsible for working with our backend. So if we want to get the post, create the post, delete the post we just need to delegate the responsibility to that class that we call it a service and this will solve the two issue that we discussed.
First of all all the details of working with the backend is encapsulated in one place and we can reuse this on multiple places. In the future if some detail is getting changed, suppose the url changes, we need to change in only one place.
The second benefit when we want Unit tests in our component we can make a fake implementation of the service that doesn’t call to the http server and we can run thousands of unit tests in just a few seconds.
Create a service
With the help of the above command the service is created but not provided, it must be provided to be used.
Open app.module.ts,
- providers: [
- PostService
- ]
Now go to the PostComponent.
Cut the Url
private url = 'http://jsonplaceholder.typicode.com/posts';
paste that in PostService.
Inject the http class inside constructor of service
Open your PostComponent and delete the http class and object in constructor. Instead use the service that you created.
Cut the http.get method from PostComponent and paste that inside the new method getPosts() under PostService.
return this.http.get(this.url)
The statement above is used to return the observable of response. We will return this observable to the consumer of response that is our PostComponent.
- ngOnInit() {
- this.service.getPosts()
- .subscribe(response=> {
- console.log(response.json());
- this.posts = response.json();
- });
- }
Separation of concern
this.service.getPosts()
The component PostComponent is telling the service that it needs to get the post. Just go and get the post irrespective of how the service is getting the post. Just give me the post. It is up to the service to figure out how to get the post. Maybe the service is getting the post from firebase db, or any storage or any format in json. It will just get the post for the consumer.
These are implementation details for how we get the post details -- the PostComponent is not affected. All the changes are encapsulated in one place inside service. Even if tens or hundreds of component are using this service they are not impacted if the service is changed in future. This is why we have separation of concern. Just like in any place we have separation of concern by assigning them a specific duty, here in Object-oriented programming also we apply the separation of concern to the class so that it will perform single responsibility.
Modify the PostComponent and make it available for one logic that is a representation of the posts, irrespective of how to create, update in actual implementation etc.
- import { Component, OnInit } from '@angular/core';
- import { PostService } from '../services/post.service';
-
- @Component({
- selector: 'app-posts',
- templateUrl: './posts.component.html',
- styleUrls: ['./posts.component.css']
- })
- export class PostsComponent implements OnInit {
- posts: any[];
- private url = 'http://jsonplaceholder.typicode.com/posts';
- constructor(private service: PostService)
- {
- }
-
- ngOnInit() {
- this.service.getPosts()
- .subscribe(response=> {
- console.log(response.json());
- this.posts = response.json();
- });
- }
-
- createPost(input: HTMLInputElement)
- {
- let post = {title: input.value};
- input.value='';
- this.service.createPost(post)
- .subscribe(response => {
- post['id'] = response.json().id;
- this.posts.splice(0,0,post);
- console.log(response.json());
- });
- }
-
- updatePost(post)
- {
- this.service.updatePost(post)
- .subscribe(response=> {
- console.log(response.json);
- })
- }
-
- deletePost(post)
- {
- this.service.deletePost(post.id)
- .subscribe(response=> {
- let index = this.posts.indexOf(post);
- this.posts.splice(index, 1);
- console.log(response.json);
- })
- }
- }
Also in PostService we can see that all the methods are working with the http endpoints.
- import { Injectable } from '@angular/core';
- import { Http } from '@angular/http';
-
- @Injectable({
- providedIn: 'root'
- })
- export class PostService {
- private url = 'http://jsonplaceholder.typicode.com/posts';
- constructor(private http: Http) { }
-
- getPosts(){
- return this.http.get(this.url)
- }
- createPost(post){
- return this.http.post(this.url, JSON.stringify(post));
- }
- updatePost(post){
- return this.http.patch(this.url + '/' + post.id, JSON.stringify({isRead: true}));
- }
- deletePost(id){
- return this.http.delete(this.url + '/' + id);
- }
- }
Thank you.