Page Contents

LoopBack AngularJS SDK автоматически создает клиентский API, который позволяет вам вызывать ваш LoopBack Node API.

Генерация lb-services.js

Для генерации Angular сервисов для LoopBack приложения, используйте инструмент командной строки AngularJS SDK lb-ng. В корневой директории проекта, введите следующую команду:

$ lb-ng server/server.js client/js/services/lb-services.js

Это команда создает client/js/services/lb-services.js.

Получение других клиентских файлов

REVIEW COMMENT from Rand
Need to set up a release in the repo, where the client directory contains everything BUT lb-services.js, since they generate it themselves.</div>

Если выполнили все предыдущие шаги из Введение в Coffee Shop Reviews приложение, то вы наверное уже клонировали себе репозиторий  loopback-getting-started-intermediate.  Если нет, то сделайте это сейчас.

Затем скопируйте client подпапку в папку вашего проекта:

$ git clone https://github.com/strongloop/loopback-getting-started-intermediate.git
$ cp -r loopback-getting-started-intermediate/client <your-app-dir>

Теперь давайте посмотрим на то, что теперь у вас есть в папке client:

  • index.html
  • css - стили
    • style.css
  • js - JavaScript файлы приложения
    • app.js
    • controllers - AngularJS контролеры 
      • auth.js
      • review.js
    • services - AngularJS сервисы
      • auth.js
      • lb-services.js
  • vendor - AngularJS библиотеки (зависимости)
    • angular-resource.js 

    • angular-ui-router.js 

    • angular.js

  • views - HTML файлы
    • all-reviews.html 

    • forbidden.html  

    • my-reviews.html  

    • sign-up-form.html

    • login.html  

    • review-form.html 

    • sign-up-success.html

Каждый файл и каталог кратко описан ниже.

index.html

Файл index.html единственный файл в верхнем уровне папки /client, и определяет основную целевую страницу приложенияand.  Давайте откроем его в редакторе:

client/index.html

<!DOCTYPE html>
<html lang="en" ng-app="app">
  <head>
    <meta charset="utf-8">
    <title>loopback-getting-started-intermediate</title>
    <link href="css/style.css" rel="stylesheet">
  </head>
  <body>
    <header>
      <h1>Coffee shop reviews</h1>
      <h2 ng-show="currentUser">Hello </h2>
      <nav>
        <ul>
          <li>
            <a ui-sref="all-reviews" ui-sref-active="active">All reviews</a>
          </li>
          <li ng-hide="currentUser">
            <a ui-sref="sign-up" ui-sref-active="active">Sign up</a>
          </li>
          <li ng-show="currentUser">
            <a ui-sref="my-reviews" ui-sref-active="active">My Reviews</a>
          </li>
          <li ng-show="currentUser">
            <a ui-sref="add-review" ui-sref-active="active">Add Review</a>
          </li>
          <li ng-hide="currentUser">
            <a ui-sref="login" ui-sref-active="active">Log in</a>
          </li>
          <li ng-show="currentUser">
            <a ui-sref="logout" ui-sref-active="active">Log out</a>
          </li>
        </ul>
      </nav>
    </header>
    <main ui-view></main>
    <script src="vendor/angular.js"></script>
    <script src="vendor/angular-resource.js"></script>
    <script src="vendor/angular-ui-router.js"></script>
    <script src="js/app.js"></script>
    <script src="js/services/lb-services.js"></script>
    <script src="js/controllers/auth.js"></script>
    <script src="js/controllers/review.js"></script>
    <script src="js/services/auth.js"></script>
  </body>
</html>

Просматривая файл вы можите увидеть ссылки на стили в папке /css, файлы клиентского JavaScript в папке /vendor и /js.

REVIEW COMMENT from Rand
Do we need to go over the CSS file?</div>

Основной клиентский JavaScript файл (app.js)

В js/app.js файле определяется конфигурация приложения.

js/app.js  

angular
  .module('app', [
    'ui.router',
    'lbServices'
  ])
  .config(['$stateProvider', '$urlRouterProvider', function($stateProvider,
    $urlRouterProvider) {
    $stateProvider
      .state('add-review', {
        url: '/add-review',
        templateUrl: 'views/review-form.html',
        controller: 'AddReviewController',
        authenticate: true
      })
      .state('all-reviews', {
        url: '/all-reviews',
        templateUrl: 'views/all-reviews.html',
        controller: 'AllReviewsController'
      })
      .state('edit-review', {
        url: '/edit-review/:id',
        templateUrl: 'views/review-form.html',
        controller: 'EditReviewController',
        authenticate: true
      })
      .state('delete-review', {
        url: '/delete-review/:id',
        controller: 'DeleteReviewController',
        authenticate: true
      })
      .state('forbidden', {
        url: '/forbidden',
        templateUrl: 'views/forbidden.html',
      })
      .state('login', {
        url: '/login',
        templateUrl: 'views/login.html',
        controller: 'AuthLoginController'
      })
      .state('logout', {
        url: '/logout',
        controller: 'AuthLogoutController'
      })
      .state('my-reviews', {
        url: '/my-reviews',
        templateUrl: 'views/my-reviews.html',
        controller: 'MyReviewsController',
        authenticate: true
      })
      .state('sign-up', {
        url: '/sign-up',
        templateUrl: 'views/sign-up-form.html',
        controller: 'SignUpController',
      })
      .state('sign-up-success', {
        url: '/sign-up/success',
        templateUrl: 'views/sign-up-success.html'
      });
    $urlRouterProvider.otherwise('all-reviews');
  }])
  .run(['$rootScope', '$state', function($rootScope, $state) {
    $rootScope.$on('$stateChangeStart', function(event, next) {
      // redirect to login page if not logged in
      if (next.authenticate && !$rootScope.currentUser) {
        event.preventDefault(); //prevent current page from loading
        $state.go('forbidden');
      }
    });
  }]);

Строки 2 - 4 включают в себя зависимости приложения:  ui.router и lbServices.  Последний является сервисом AngularJS библиотеки который вы сгенерировали ранее используя lb-ng.

Строки 61 - 66 определения перехватчика, который вызывается когда происходит изменения состояния: если пользователь не авторизован его перенаправит на страницу с запретом.

Остальные строки определяют состояние приложения. Состоянием определяться какие страницы будут видны, когда пользователь будет переходить по URLs или нажмет на ссылку. Любое состояние для которого  authenticate является true, требует, чтоб вы авторизовались сначала. Если вы перейдете по одному из этих адресов непосредственно то вы увидите страницу с ошибкой запрета доступа (state = forbidden, url = /forbidden). Каждый вызов state() задает шаблон  для состояния и контролера и вызывает проверку на требование авторизации.  

В следующей таблице приведены состояния и как они соответствуют контролерам, шаблонам и URL.

Состояние URL Описнаие Контроллер Просмотр/ Шаблон Должен ли быть авторизованым?
'add-review'

/add-review

Добавление отзыва. AddReviewController

review-form.html

Да
'all-reviews' /all-reviews Список отзывов. AllReviewsController all-reviews.html Нет
'edit-review' /edit-review/:id Редактирование выбранного отзыва EditReviewController review-form.html Да
'delete-review' /delete-review/:id Удаление выбранного отзыва DeleteReviewController None Да
'forbidden' /forbidden

Ошибка запрещенного URL.

  • Уведомляет пользователя, что он не может выполнить данное действие
  • Отображает ссылку на страницу входа в ситсему
EditReviewController forbidden.html Нет
'login' /login

Авторизация

Перенаправляет на страницу добавления отзыва после авторизации

AuthLoginController login.html Нет
'logout' /logout

Выйти

  • Уведомляет пользователя что он вышел из системы
  • Отображает ссылку на страницу всех отзывов
AuthLogoutController Нет Нет
'my-reviews' /my-reviews Список юзера который авторизовался MyReviewsController my-reviews.html Да
'sign-up' /sign-up Регестрация SignUpController sign-up-form.html Нет
'sign-up-success' /sign-up/success

Успешная регистрация.

Отображает ссылку на страницу всех отзывов.

Нет sign-up-success.html Нет

Контроллеры

ВAngular, контроллер  это функция JavaScript конструктора которая используется для усиления Angular Scope.

Когда контролер подключен к DOM через директивы ng-controller, Angular будет создавать новый Controller  объект, используя функцию конструктора. Новый дочерняя область (scope) будут доступны в виде инъекционного параметра функции конструктора $scope. Для получения более, see Understanding Controllers (AngularJS documentation).

client/js/controllers папка содержит два файла которые определяют контролерыauth.js и review.js.

Контролер auth.js обрабатывает регистрацию пользователей, авторизацию и выход.  Когда пользователь входит в систему, в currentUser объект установлен в корневой области (scope).  Другие части приложения проверяют currentUser объект при выполнении действии.  Когда пользователь выходит, currentUser объект разрушается.

js/controllers/auth.js  

angular
  .module('app')
  .controller('AuthLoginController', ['$scope', 'AuthService', '$state',
    function($scope, AuthService, $state) {
      $scope.user = {
        email: 'foo@bar.com',
        password: 'foobar'
      };
      $scope.login = function() {
        AuthService.login($scope.user.email, $scope.user.password)
          .then(function() {
            $state.go('add-review');
          });
      };
    }
  ])
  .controller('AuthLogoutController', ['$scope', 'AuthService', '$state',
    function($scope, AuthService, $state) {
      AuthService.logout()
        .then(function() {
          $state.go('all-reviews');
        });
    }
  ])
  .controller('SignUpController', ['$scope', 'AuthService', '$state',
    function($scope, AuthService, $state) {
      $scope.user = {
        email: 'baz@qux.com',
        password: 'bazqux'
      };
      $scope.register = function() {
        AuthService.register($scope.user.email, $scope.user.password)
          .then(function() {
            $state.transitionTo('sign-up-success');
          });
      };
    }
  ]);

Другой файл, review.js, определяет контролеры для действий отзывов (review).

 Expand source

angular
  .module('app')
  .controller('AllReviewsController', ['$scope', 'Review', function($scope,
    Review) {
    $scope.reviews = Review.find({
      filter: {
        include: [
          'coffeeShop',
          'reviewer'
        ]
      }
    });
  }])
  .controller('AddReviewController', ['$scope', 'CoffeeShop', 'Review',
    '$state',
    function($scope, CoffeeShop, Review, $state) {
      $scope.action = 'Add';
      $scope.coffeeShops = [];
      $scope.selectedShop;
      $scope.review = {};
      $scope.isDisabled = false;
      CoffeeShop
        .find()
        .$promise
        .then(function(coffeeShops) {
          $scope.coffeeShops = coffeeShops;
          $scope.selectedShop = $scope.selectedShop || coffeeShops[0];
        });
      $scope.submitForm = function() {
        Review
          .create({
            rating: $scope.review.rating,
            comments: $scope.review.comments,
            coffeeShopId: $scope.selectedShop.id
          })
          .$promise
          .then(function() {
            $state.go('all-reviews');
          });
      };
    }
  ])
  .controller('DeleteReviewController', ['$scope', 'Review', '$state',
    '$stateParams',
    function($scope, Review, $state, $stateParams) {
      Review
        .deleteById({
          id: $stateParams.id
        })
        .$promise
        .then(function() {
          $state.go('my-reviews');
        });
    }
  ])
  .controller('EditReviewController', ['$scope', '$q', 'CoffeeShop', 'Review',
    '$stateParams', '$state',
    function($scope, $q, CoffeeShop, Review,
      $stateParams, $state) {
      $scope.action = 'Edit';
      $scope.coffeeShops = [];
      $scope.selectedShop;
      $scope.review = {};
      $scope.isDisabled = true;
      $q
        .all([
          CoffeeShop.find().$promise,
          Review.findById({
            id: $stateParams.id
          }).$promise
        ])
        .then(function(data) {
          var coffeeShops = $scope.coffeeShops = data[0];
          $scope.review = data[1];
          $scope.selectedShop;
          var selectedShopIndex = coffeeShops
            .map(function(coffeeShop) {
              return coffeeShop.id;
            })
            .indexOf($scope.review.coffeeShopId);
          $scope.selectedShop = coffeeShops[selectedShopIndex];
        });
      $scope.submitForm = function() {
        $scope.review.coffeeShopId = $scope.selectedShop.id;
        $scope.review
          .$save()
          .then(function(review) {
            $state.go('all-reviews');
          });
      };
    }
  ])
  .controller('MyReviewsController', ['$scope', 'Review', '$rootScope',
    function($scope, Review, $rootScope) {
      $scope.reviews = Review.find({
        filter: {
          where: {
            publisherId: $rootScope.currentUser.id
          },
          include: [
            'coffeeShop',
            'reviewer'
          ]
        }
      });
    }
  ]);

Следующая таблица описывает контроллеры, определенные в review.js.

Контролеры Описание
AllReviewsController Выполняет Review.find() для получение отзывов. Использует включаемый модуль для добавления coffeeShop и модели отзыва.  Это возможно из-за связи определенной выше.
AddReviewController

Кофейни заполняются с сервера при первой загрузке странице, через CoffeeShop.find() меню.

Когда форма будет отправлена, мы создаем отзыв и изменяем страницу всех отзывов когда права позволяют.

DeleteReviewController Не отображает при соответствии этому состоянию, когда вызывается; соответствующий данному ID отзыв удаляется.  ID находится в URL.
EditReviewController

Похож на  AddReviewController когда страница первый раз загружается.  

Приложение выполнит два запроса одновременно используя $q, чтобы получить необходимые модели. С помощью этих модели мы получаем выпадающее меню с доступными кофейнями. После того, как приложение отображает кофейне в выпадающем списке, и выбирает кофейню ранее выбранную в первоначальном обзоре. Затем приложение компонует coffeeShopId  с выбранной кофейней.

MyReviewController

Как AllReviewsController, этот контроллер использует "where" фильтр, чтобы ограничить результирующий набор, основанный на publisherId, где publisherId определяется текущим вошедшим в систему пользователем.  Затем он использует подключаемый фильтр включающий Coffeeshop и модель рецензент (reviewer ).

Сервисы

Anоgular сервисы взаимозаменяемые объекты, которые соединены друг с другом с помощью зависимых иньекций (DI). Вы можете воспользоваться сервисами чтоб организовать совместное использование кода посредством вашего приложения.

Папка js/services содержит две AngularJS сервис библиотеки: auth.js и lb-services.js.

Вы сгенерировали lb-services.js ранее, и это описано в  Генерация lb-services.js

Другой файл, auth.js, предоставляет простой интерфейс для механизмов аутентификации низкого уровня. Он использует модель  Reviewer (рецензент)  (которая расширяет базовую User модель ) и определяет следующие сервисы:

  • login: регистрирует пользователя в  inLoopback автоматически управляет ключом безопасности (authentication token), который хранится в локальном HTML5 хранилище  браузера.
  • logout: регистрирует выход пользователя.  Сохраняет ключ в локальном HTML5 хранилище браузера localstorage.
  • register: регистрирует нового пользователя с помощью прилагаемого email и пароля, с минимальными требованиями для создания нового пользователя LoopBack.

js/services/auth.js  Expand source

angular
  .module('app')
  .factory('AuthService', ['Reviewer', '$q', '$rootScope', function(User, $q,
    $rootScope) {
    function login(email, password) {
      return User
        .login({
          email: email,
          password: password
        })
        .$promise
        .then(function(response) {
          $rootScope.currentUser = {
            id: response.user.id,
            tokenId: response.id,
            email: email
          };
        });
    }

    function logout() {
      return User
        .logout()
        .$promise
        .then(function() {
          $rootScope.currentUser = null;
        });
    }

    function register(email, password) {
      return User
        .create({
          email: email,
          password: password
        })
        .$promise;
    }
    return {
      login: login,
      logout: logout,
      register: register
    };
  }]);

Вид

Папка client/views содержит семь “partial” шаблонов для просмотра, которые client/index.html использует ngView директиве. ”partial” сегмент шаблона в самом файле HTML. 

Приведенная  выше таблица  описывает, каким образом вид соответствует состояниям и контроллерам.