The desire to develop your own Angular.js webApi module arose when working with a large number of http requests in the project.
It was important not just to create a file with constants, but to develop a module to simplify the support of the existing functionality. In turn, it was necessary to take care of possible subsequent expansion without violating the integrity of the current core of the module.
Tasks that the future webApi module should solve:
Demo
View the source code of the module: github .
Further we will talk about each of these points in more detail.
It is about using one request in several places of an application (controllers, services, factories). When the methods are 10-20, then making changes to each request will not be a big problem. But when it comes to the number of 100, 200 and more url'ov, support for such a code causes more and more difficulties.
Example # 1
There is a method that gets a list of user groups. Suppose it is displayed in the corresponding dropdown. When a group is selected, its users are loaded by another request, where the " id " of the group is transmitted. Also on the page there is an additional functionality for visualizing some user data.
// $http.get("api/group-manage/get-all") // $http.get("api/group-manage/2/get-users")
In another controller there is a table where you need to display all users of the system for a specific group. This page provides much more opportunities for user analysis and management.
// $http.get("api/group-manage/2/get-users")
Actually similar requests can be much more.
Solution to the problem
, http- , .. url'.
This approach will save time when searching for the necessary query in the project and its modification.
Again, working with such a file will only work if there are a small number of requests to the server. It is hard to imagine a convenient support for a file with constants, which are more than 200.
This approach will solve the resulting problem. How exactly the definition of independent categories will take place is determined by the developer. For simplicity, you can focus on the name of the controller method from api
.
// http://yourapi.com/api/group-manage/2/get-users // http://yourapi.com/api/group-manage/get-all
From the example above it can be seen that the queries have a common root /api/group-manage/
. Create a category with the appropriate name groupManage.js
.
In the Angular.js environment, this file is declared as constant , which is later connected to the main functionality of the webApi module.
There may be several such groups in the project. But now we definitely know where to look for requests related to the management of groups.
If you call the added url directly, then sooner or later there will be a series of similar dependencies in the code. Therefore, it is necessary to create a common block that provides a list of all existing requests for operating with them in the " core " webApi.
One of the most difficult tasks was to develop a kernel that can handle all requests to the server, while not only not revealing its internal implementation, but also providing the ability to easily configure the webApi module for a specific Angular.js application.
An example query is as follows:
{ Url: '/api/acc/login', CustomOptions: false, Method: 'post', InvokeName: 'login' }
This approach allows not only to group several url by a common type, but also to have an idea what each method will do with the data.
In the webApi/config/
directory there is a file with API settings. This is where we specify the DOMAIN url.
Example # 2
Almost all modern Angular.js applications work with an authentication system. This is usually a post-method that sends user data to the server (login, password).
With successful respons'e, the main route of the application is notified, after which the user will be redirected to the page with the functionality.
Call the method:
webApi.login({ "Login": "user", "Password": "qwerty" }).success(function(response, status, headers, config){ // - })
Thus, we are working at a special level of abstractions, which allows us to focus on the process of building the logic of the application. It is advisable to give adequate names to the methods so that you can understand what a specific call is doing:
// { Url: '/api/acc/logout', CustomOptions: false, Method: 'get', InvokeName: 'logout' } // - webApi.logout([]);
Probably, it is not at all clear how to use an empty array in the get-method, but then everything will be discussed later.
Quite often, when developing an application, the server-side provides the client with the following request format:
And so that when sending a request to the server in the right place of the application not to perform unnecessary operations with the construction of such a url, a special component was developed that automatically generates the correct address based on the received parameters.
// { Url: '/api/admin/update/profile/{id}/block', CustomOptions: false, Method: 'put', InvokeName: 'blockAdminProfileById' } // - webApi.blockAdminProfileById({ "url": { "id": 5 } });
Generated request: /api/admin/update/profile/5/block
(plus domain url, of course).
And if we need to send a more complex request to the server (for example, blocking duration and type), then we simply specify the remaining parameters as fields of the "url" object:
// { Url: '/api/admin/update/profile/{id}/block/{type}/{time}', CustomOptions: false, Method: 'put', InvokeName: 'blockAdminProfileById' } // - webApi.blockAdminProfileById({ "url": { "id": 5, "type": "week", "time": 2 } });
Generated request: /api/admin/update/profile/5/block/week/2
. And now the user will be blocked by the system for 2 weeks.
Templating works for all types of requests, including get. It is desirable to form all requests in this way: we save time, do not litter the internal code with unnecessary operations.
Data transfer in request body
It should be noted that if you want to send in addition to the template url to the server also some data (for example, a post request), you need to send them as follows:
webApi.createPost({ "data": { "title": "Test post", "category": "sport", "message": "Content..." } });
Naturally, you can use both url-templating and transferring an object with data. The latter will be sent to the server in the request body.
Work with GET methods
No data in the request body is transmitted here, but everyone knows that a get request can be generated like this:
api/admin/news/10?category=sport&period=week
or so:
api/admin/manage/get-statistic/5/2016
or so:
api/admin/manage/get-all
.
Consider each of the options for generation.
// Case #1 -> api/admin/manage/get-all // -> "Url" : 'api/admin/manage/get-all', ... // webApi.getAllAdmins([]).success(//...) // Case #2 -> api/admin/manage/get-statistic/5/2016 // -> "Url" : 'api/admin/manage/get-statistic/{id}/{year}', ... // webApi.getAdminStatsticById({ "url": { "id": 5, "year": 2016 } }).success(//...) // Case #3 -> admin/news/10?category=sport&period=week // -> "Url" : 'admin/news', ... // webApi.getNews({ before: ['10'], after: { "category": "sport", "period": "week" } }).success(//...)
We already dealt with the second type of requests above.
In the first case, we always pass an empty collection when you just need to send a request to the server.
In case # 3, the before field defines a series of parameters that go as far as the "?" , and the after field is the key-value set. Naturally, in some cases, you can leave before an empty collection [].
CustomOptions option in settings
Get request without templating url:
webApi.getNewsById([10, {"headers": {"Content-Type": "text/plain"} } ]);
In all other cases (including get-requests with url templating):
webApi.login({ options: {"timeout": 100, {"headers": {"Content-Type": "text/plain"} } });
The structure of the module is as follows:
You will have to work with the last two directories.
Example # 3
Suppose a book accounting system is being developed at a university. A high probability of splitting queries into the following groups:
Of course, this list can be expanded.
- , .
(function(){ angular .module("_webApi_") .constant("cat.account", { "DATA": [ { Url: '/api/acc/login', CustomOptions: false, Method: 'post', InvokeName: 'login' }, // ] }); })();
Request file created. Now we need to connect it with our webApi core.
(function(){ angular .module("_webApi_") .service("webApi.requests", webApiRequests); function webApiRequests(catAccount){ // // , } // IoC container. webApiRequests.$inject = [ "cat.account" ]; })();
In this case, all the constants are written using the " cat.constant name ", and are connected to the recorder " catName Constants ".
Thus, webApi uses the extra namespace " cat. " To avoid conflicts with other constants in the application.
And now we call the method according to the template described:
webApi.login( //- )
In order not to reveal the webApi functionality to the internal code of the application, it was decided to use the additional abstraction "repository".
, , http-. -webApi , .
Suppose we have a “ FoodController ” and the corresponding foodManage request group. Each method from this category is responsible for its specific implementation of data management on the server.
We announce the repository:
(function() { "use strict"; angular .module("app.repositories") .factory("repository.food", foodRepository); function foodRepository(webApi){ return { get: getById getAll: getAll, remove: removeById, removeAll: removeAll } // , function getById(id){ return webApi.getFoodItemById({ "url": { "id": id } }); } function getAll(){ return webApi.getAllFoodItems([]); } // } // IoC container. foodRepository.$inject = [ "webApi" ]; })();
Thus, we have created an abstract factory for managing data from the controller, without revealing the functionality of webApi. And at any time we can replace the current implementation with any other.
For example, when receiving information about the item, we now need to specify the type: "vegetables", "fruits", "milk-made", etc. Due to the presence of a special level of abstraction, we simply need to make the following changes to the method:
function getById(id, category) { return webApi.getFoodItemById({ "url": { "id": id, "category": category } }); }
Connecting the repository to the application
As mentioned above, the repository is an entity that provides open methods for managing data through an internal call to the webApi module.
Therefore, it will be enough for us to call a specific method and pass the corresponding parameters to it.
(function() { "use strict"; angular .module("app") .controller("FoodController", FoodController); function FoodController(foodRepository){ /* jshint validthis:true */ var vm = this; // , vm.foodItems = []; vm.getAllFood = function(){ foodRepository.getAll().success(function(response){ vm.foodItems = response.data; }); }; // vm.getAllFood(); } // IoC container. FoodController.$inject = [ "repositories.food" ]; })();
HTML snippet for data visualization:
<div ng-controller="FoodController as fc"> <ul> <li ng-repeat="food in fc.foodItems track by food.Id"> Title: <span class="item-title"> {{food.title}} </span> Cost: <span class="item-cost"> {{food.cost}} </span> </li> </ul> </div>
Thus, the repository returns data and Angular.js automatically substitutes it in the view. And we cannot directly access webApi by deleting some item there or adding it without agreeing with the central controller.
, .
We considered the option of creating a configurable and extensible webApi module for working with Angular.js.
This approach will help you get rid of duplication of logic in the code, reduce the time for editing the required method, as well as facilitate the work with complex queries.
Source: https://habr.com/ru/post/282397/
All Articles