Статьи

AngularJS: Представляем модули, контроллеры, сервисы

В моем предыдущем посте AngularJS Tutorial: Начало работы с AngularJS мы видели, как настроить приложение, используя SpringBoot + AngularJS + WebJars. Но это своего рода руководство по быстрому старту, в котором я мало что объяснил о модулях, контроллерах и сервисах AngularJS. Также это одноэкранное приложение (только один маршрут).

В этом уроке части 2 мы рассмотрим, что такое Angular модули, контроллеры и сервисы и как их настраивать и использовать. Также мы рассмотрим, как использовать ngRoute для создания многоэкранного приложения.

Если мы посмотрим на код, который мы разработали в предыдущем посте, особенно в controllers.js , мы объединили клиентскую логику контроллера и бизнес-логику (конечно, у нас здесь нет никакой логики biz!) В наших контроллерах, которые не хорошо.

Как Java-разработчики, мы привыкли иметь дюжину слоев, и нам нравится делать вещи сложными и жаловаться, что Java сложна. Но здесь, в AngularJS, все выглядит проще, давайте немного усложним. Я просто шучу !

Даже если вы разместите всю свою логику в одном месте, как мы это делали в controllers.js , она будет работать и приемлема для простых приложений. Но если вы собираетесь разрабатывать большие корпоративные приложения (которые сказали, что корпоративные приложения должны быть большими … хм … ладно … продолжайте …), тогда все быстро станет грязным. И поверьте мне, работать с большой грязной кодовой базой JavaScript намного сложнее, чем с большой грязной кодовой базой Java. Поэтому неплохо отделить бизнес-логику от логики контроллера.

В AngularJS мы можем организовать логику приложения в модули и заставить их работать вместе, используя внедрение зависимостей. Давайте посмотрим, как создать модуль в AngularJS.

1
var myModule = angular.module('moduleName',['dependency1','dependency2']);

Вот как мы можем создать модуль с помощью функции angular.module (), передав имя модуля и указав список зависимостей, если они есть.

Как только мы определим модуль, мы можем получить дескриптор модуля следующим образом:

1
var myModule = angular.module('moduleName');

Заметьте, что здесь нет второго аргумента, который означает, что мы получаем ссылку на предопределенный угловой модуль. Если вы включите второй аргумент, который является массивом, то это означает, что вы определяете новый модуль.

Как только мы определим новый модуль, мы можем создать контроллеры в этом модуле следующим образом:

1
2
3
module.controller('ControllerName',['dependency1','dependency2', function(dependency1, dependency2){
//logic
}]);

Например, давайте посмотрим, как нам создать TodoController .

1
2
3
4
var myApp = angular.module('myApp',['ngRoute']);
myApp.controller('TodoController',['$scope','$http',function($scope,$http){
//logic
}]);

Здесь мы создаем TodoController и предоставляем $ scope и $ http как зависимости, которые являются встроенными сервисами angularjs.

Мы также можем создать тот же контроллер следующим образом:

1
2
3
myApp.controller('TodoController',function($scope,$http){
//logic
});

Заметьте, что мы напрямую передаем функцию в качестве второго аргумента вместо массива с массивом зависимостей, за которым следует функция, которая принимает те же зависимости, что и аргументы, и работает точно так же, как объявление на основе массива.

Но зачем нам больше печатать, когда оба делают одно и то же?

AngularJS внедряет зависимости по имени, что означает, что когда вы определяете $ http как зависимость, AngularJS ищет зарегистрированный сервис с именем ‘ $ http ‘. Но большинство реальных приложений используют инструменты минимизации кода JavaScript для уменьшения размера. Эти инструменты могут переименовывать ваши переменные в короткие имена переменных.

Например:

1
2
3
myApp.controller('TodoController',function($scope,$http){
//logic
});

Предыдущий код может быть уменьшен до:

1
2
3
myApp.controller('TodoController',function($s,$h){
//logic
});

Затем AngularJS пытается найти зарегистрированные сервисы с именами $ s и $ h вместо $ scope и $ http, и в конце концов это не удастся. Чтобы преодолеть эту проблему, мы определяем имена сервисов как строковые литералы в массиве и указываем те же имена, что и аргументы функции. При этом даже после того, как JavaScript минимизирует имена аргументов функции, строковые литералы остаются неизменными, и AngularJS выбирает нужные сервисы для внедрения.

Это означает, что вы можете написать контроллер следующим образом:

1
2
3
myApp.controller('TodoController',['$scope','$http',function($s,$h){
//here $s represents $scope and $h represents $http services
}]);

Поэтому всегда предпочитайте использовать подход на основе массива зависимостей.

Хорошо, теперь мы знаем, как создавать контроллеры. Давайте посмотрим, как мы можем добавить некоторые функциональные возможности для наших контроллеров.

01
02
03
04
05
06
07
08
09
10
11
12
myApp.controller('TodoController',['$scope','$http',function($scope,$http){
    var todoCtrl = this;
    todoCtrl.todos = [];
    todoCtrl.loadTodos = function(){
        $http.get('/todos.json').success(function(data){
            todoCtrl.todos = data;
        }).error(function(){
            alert('Error in loading Todos');
        });
    };
    todoCtrl.loadTodos();
}]);

Здесь, в нашем TodoController, мы определили переменную todos, которая изначально содержит пустой массив, и определили функцию loadTodos (), которая загружает задачи из сервисов RESTful, используя $ http.get (), и после получения ответа мы устанавливаем массив todos в нашу переменную todos . Просто и прямо.

Почему мы не можем напрямую присвоить ответ $ http.get () нашей переменной todos, например todoCtrl.todos = $ http.get (‘/ todos.json’); ??

Поскольку $ http.get (‘/ todos.json’) возвращает обещание , а не фактические данные ответа. Таким образом, вы должны получить данные из функции обработчика успеха. Также обратите внимание, что если вы хотите выполнить какую-либо логику после получения данных из $ http.get (), вы должны поместить свою логику только в функцию-обработчик успеха.

Например, если вы удаляете элемент Todo, а затем перезагружаете задачи, вы НЕ должны делать следующее:

1
2
3
4
5
6
$http.delete('/todos.json/1').success(function(data){
//hurray, deleted
}).error(function(){
alert('Error in deleting Todo');
});
todoCtrl.loadTodos();

Здесь вы можете предположить, что после того, как удаление будет завершено, он загрузит Todos () и удаленный элемент Todo не будет отображаться, но это не сработает. Вы должны сделать это следующим образом:

1
2
3
4
5
6
$http.delete('/todos.json/1').success(function(data){
//hurray, deleted
todoCtrl.loadTodos();
}).error(function(){
alert('Error in deleting Todo');
});

Перейдем к тому, как создавать сервисы AngularJS. Создание сервисов также похоже на контроллеры, но AngularJS предоставляет несколько способов создания сервисов.

Существует 3 способа создания сервисов AngularJS:

  • Использование module.factory ()
  • Использование module.service ()
  • Использование module.provider ()

Использование module.factory ()

Мы можем создать сервис с помощью module.factory () следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
angular.module('myApp')
.factory('UserService', ['$http',function($http) { 
  var service = {
    user: {},
    login: function(email, pwd) {
      $http.get('/auth',{ username: email, password: pwd}).success(function(data){
        service.user = data;
      });     
    },
    register: function(newuser) {
      return $http.post('/users', newuser);
    }
  };
  return service;
}]);

Использование module.service ()

Мы можем создать сервис с помощью module.service () следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
angular.module('myApp')
.service('UserService', ['$http',function($http) {
    var service = this;
    this.user = {};
    this.login = function(email, pwd) {
      $http.get('/auth',{ username: email, password: pwd}).success(function(data){
        service.user = data;
      });     
    };
    this.register = function(newuser) {
      return $http.post('/users', newuser);
    };
   
}]);

Использование module.provider ()

Мы можем создать сервис с помощью module.provider () следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
angular.module('myApp')
.provider('UserService', function() {
  return {
      this.$get = function($http) {
        var service = this;
        this.user = {};
        this.login = function(email, pwd) {
          $http.get('/auth',{ username: email, password: pwd}).success(function(data){
            service.user = data;
          });     
        };
        this.register = function(newuser) {
          return $http.post('/users', newuser);
        };
      }
  }
});

Вы можете найти хорошую документацию о том, какой метод подходит и в каком сценарии, по адресу http://www.ng-newsletter.com/advent2013/#!/day/1 .

Давайте создадим TodoService в нашем файле services.js следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
var myApp = angular.module('myApp');
 
myApp.factory('TodoService', function($http){
    return {
        loadTodos : function(){
            return $http.get('todos');
        },     
        createTodo: function(todo){
            return $http.post('todos',todo);
        },     
        deleteTodo: function(id){
            return $http.delete('todos/'+id);
        }
    }
});

Теперь добавьте наш TodoService в наш TodoController следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
myApp.controller('TodoController', [ '$scope', 'TodoService', function ($scope, TodoService) {
     
    $scope.newTodo = {};
     
    $scope.loadTodos = function(){
        TodoService.loadTodos().
        success(function(data, status, headers, config) {
            $scope.todos = data;
         })
        .error(function(data, status, headers, config) {
              alert('Error loading Todos');
        });
    };
     
    $scope.addTodo = function(){
        TodoService.createTodo($scope.newTodo).
        success(function(data, status, headers, config) {
            $scope.newTodo = {};
            $scope.loadTodos();
         })
        .error(function(data, status, headers, config) {
              alert('Error saving Todo');
        });
    };
     
    $scope.deleteTodo = function(todo){
        TodoService.deleteTodo(todo.id).
        success(function(data, status, headers, config) {
            $scope.loadTodos();
         })
        .error(function(data, status, headers, config) {
              alert('Error deleting Todo');
        });
    };
     
    $scope.loadTodos();
}]);

Теперь мы разделили логику нашего контроллера и бизнес-логику, используя контроллеры и сервисы AngularJS, и заставили их работать вместе, используя Dependency Injection.

В начале поста я сказал, что мы будем разрабатывать многоэкранное приложение, демонстрирующее функциональность ngRoute .

В дополнение к Todos, давайте добавим функцию PhoneBook в наше приложение, где мы можем вести список контактов.

Во-первых, давайте создадим внутреннюю функциональность для служб PhoneBook REST.

Создайте сущность Person JPA, ее репозиторий Spring Data JPA и контроллер.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Entity
public class Person implements Serializable
{
    private static final long serialVersionUID = 1L;
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private Integer id;
    private String email;
    private String password;
    private String firstname;
    private String lastname;
    @Temporal(TemporalType.DATE)
    private Date dob;
         
    //setters and getters
}
 
public interface PersonRepository extends JpaRepository<Person, Integer>{
 
}
 
@RestController
@RequestMapping("/contacts")
public class ContactController {
    @Autowired
    private PersonRepository personRepository;
    @RequestMapping("")
    public List<Person> persons() {
        return personRepository.findAll();
    }
}

Теперь давайте создадим AngularJS сервис и контроллер для контактов. Заметьте, что на этот раз мы будем использовать подход module.service () .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
myApp.service('ContactService', ['$http',function($http){
     
        this.getContacts = function(){
            var promise = $http.get('contacts')
                                .then(function(response){
                                    return response.data;
                                },function(response){
                                    alert('error');
                                });
            return promise;
        }
    }
}]);
 
myApp.controller('ContactController', [ '$scope', 'ContactService', function ($scope, ContactService) {
    ContactService.getContacts().then(function(data) {
        $scope.contacts = data;
    });
    }
]);

Теперь нам нужно настроить маршруты нашего приложения в файле app.js.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
var myApp = angular.module('myApp',['ngRoute']);
 
myApp.config(['$routeProvider','$locationProvider',
        function($routeProvider, $locationProvider) {
          $routeProvider
            .when('/home', {
              templateUrl: 'templates/home.html',
              controller: 'HomeController'
            })
            .when('/contacts', {
              templateUrl: 'templates/contacts.html',
              controller: 'ContactController'
            })
            .when('/todos', {
                templateUrl: 'templates/todos.html',
                controller: 'TodoController'
            })          
            .otherwise({
                redirectTo: 'home'
            });
}]);

Здесь мы настроили маршруты нашего приложения на $ routeProvider внутри функции myApp.config () .

Когда url совпадает с любым из маршрутов, соответствующее содержимое шаблона будет отображено в <div ng-view> </ div> div в нашем index.html .

Если URL-адрес не совпадает ни с одним из настроенных URL-адресов, он будет перенаправлен в « home », как указано в конфигурации else () .

У наших templates / home.html пока что ничего не будет, и файл templates / todos.html будет таким же, как home.html в предыдущем посте.

Новый шаблон / contacts.html будет просто иметь таблицу, в которой перечислены контакты следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<table class="table table-striped table-bordered table-hover">
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="contact in contacts">
            <td>{{contact.firstname + ' '+ (contact.lastname || '')}}</td>
            <td>{{contact.email}}</td>         
        </tr>
    </tbody>
</table>

Теперь давайте создадим навигационные ссылки на страницы Todos, Contacts на нашей странице index.html <body> .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<div class="container">                
  <div class="row">
    <div class="col-md-3 sidebar">
        <div class="list-group">
          <a href="#home" class="list-group-item">
            <i class="fa fa-home fa-lg"></i> Home
          </a>                
          <a href="#contacts" class="list-group-item">
            <i class="fa fa-user fa-lg"></i> Contacts
          </a>
          <a href="#todos" class="list-group-item">
            <i class="fa fa-indent fa-lg"></i> ToDos
          </a>         
        </div>
     </div>
     <div class="col-md-9 col-md-offset-3">
        <div ng-view></div>
     </div>
  </div>
</div>

К настоящему времени у нас есть многоэкранное приложение, и мы поняли, как использовать модули, контроллеры и сервисы.

Вы можете найти код этой статьи по адресу https://github.com/sivaprasadreddy/angularjs-samples/tree/master/angularjs-series/angularjs-part2.

Наша следующая статья будет о том, как использовать $ resource вместо $ http для использования служб REST.

Также мы рассмотрим обновление нашего приложения для использования более мощного модуля ui-router вместо ngRoute . Оставайтесь в курсе !