0,0
рейтинг
17 августа 2012 в 13:59

Разработка → Практикум AngularJS — разработка административной панели из песочницы

При создании сайтов часто возникает задача создания админки для редактирования контента. Задача, в общем, тривиальная, но сделать удобную админку не так-то просто.

Под удобством в первую очередь подразумевается возможность сортировки таблицы со списком материалов и работа без перезагрузки страницы. Если материалов в таблице становится много, то возникает задача разбивать её на страницы.

Всем известный jQuery-плагин tablesorter с tablesorterPager-ом и менее известный, но гораздо более функциональный DataTables хороши, но обладают некоторыми недостатками. Главный из них — сложность динамического добавления новых строк в таблицу (после добавления строки в таблицу, новая строка потеряется при следующем вызове сортировки). tablesorter вообще не даёт средств для добавления строки в свой кэш, DataTables предоставляет широкое и функциональное API для управления внутренним представлением таблицы, но это API довольно многословно и не очень гибко.

Хочу предоставить общественности реализацию админки на относительно новой javascript-фреймворке AngularJS. Будет создана страничка для редактирования списка вопросов, разбитых по категориям и отвечающим. В статье нет сравнения с другими подобными фреймворками, но нет и простого повторения официальной документации, я постараюсь поделиться своим опытом в использовании фреймворка и расскажу о нескольких интересных приёмах работы с ним.

Сразу приведу, что получится в итоге (кликабельно):



Вступление


Несколько слов о фреймворке я всё-таки приведу. AngularJS представялет собой Javascript MVC-фреймворк, проект основан Google-ом. Включает в себя собственную высокоуровневую реализацию ajax, встроенные средства unit- и e2e-тестов (Jasmine для unit-тестирования, для end-to-end тестов запускается специальный сервер тестирования). Тестирование я рассматривать не буду, это тема отдельной статьи. Подробнее о фреймворке недавно написал aav в своём посте.

Впервые встретился с ним в статье «7 причин, почему AngularJS крут». К сожалению, кроме официальной документации (кстати, довольно неплохой), я нашёл только одну статью, описывающую работу с AngularJS (правда, не самую новую версию). Также для начального знакомства c фреймворком рекомендую пройти официальный тур.

Основы фреймворка AngularJS


Перейдём собственно к разработке админки. Индексный файл index.html загружается в браузер, и дальше мы с него никуда не уйдём, вся работа будет происходить с помощью динамической загрузки. Сам файл ничего особенного не содержит. В нём важно два момента – атрибут ng-app=«admin» тега <html> и раздел <div ng-view></div>, в который будут помещаться наши странички.
<!doctype html>
<html lang="ru" ng-app="admin">
<head>
  <meta charset="utf-8">
  <title>Admin page - Questions</title>
  <link rel="stylesheet" href="css/app.css"/>
  <link rel="stylesheet" href="css/bootstrap.css"/>
  <link rel="stylesheet" href="css/bootstrap-responsive.css"/>
</head>
<body>
  <div ng-view></div>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
  <script>!window.jQuery && document.write(unescape('%3Cscript src="/js/jquery.js"%3E%3C/script%3E'))</script>
  <script src="lib/angular/angular.js"></script>
  <script src="lib/angular/angular-resource.js"></script>
  <script src="js/app.js"></script>
  <script src="js/services.js"></script>
  <script src="js/controllers.js"></script>
  <script src="js/directives.js"></script>
</body>
</html>

Как можно увидеть, AngularJS оперирует расширенными атрибутами тегов – директивами. Директивы можно записывать несколькими способами, следующие записи идентичны: ng-app=”admin”, data-ng-app=”admin”, также существует ещё несколько методов. Также возможно разрабатывать свои директивы.

AngularJS предлагает разбивать код приложения по нескольким файлам. app.js – инициализация приложения, роутинг, services.js – создание различных сервисов, описание удалённых ресурсов (например, для ajax-загрузки данных), которые потом можно использовать в контроллерах, controllers.js – собственно контроллеры, filters.js – фильтры, используются при выводе данных, directives.js – создание собственных директив для html.
Файл app.js:
'use strict';

angular.module('admin', ['admin.services','admin.filters'])
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider
      .when('/list', {template: 'views/list.html', controller: ListCtrl})
      .when('/new', {template: 'views/edit.html', controller: NewCtrl})
      .when('/edit/:id', {template: 'views/edit.html', controller: EditCtrl})
      .otherwise({redirectTo: '/list'});
  },
]);

Тут мы назначаем маршруты для наших вьюшек. Кстати, выглядеть это будет в виде myadmin.com/#/list (можно сделать и #!, который Гугл принял за стандарт для индексирования, спасибо aav за комментарий). Вьюшки я расположил в папку /views/ (в отличие от предлагаемого создателями /partials/). Интересно, что AngularJS предлагает везде включать строгий режим 'use strict' (в этой статье про use strict подробнее).

Дальше я приведу упрощённый вариант списка материалов, который по ходу статьи будет дополняться. Я считаю, что пошаговое развитие будет для читателей полезнее и понятнее.
Файл /views/list.html:
<div id="table-wrapper">
<div class="filter tools pull-right">
Фильтр <input ng-model="filterStr" class="search-query">
</div>
<div class="tools pull-left">
  <a href="#/new" class="btn btn-success">Создать новую запись</a>
</div>
<table class="table table-striped">
<thead>
  <tr>
    <th ng-repeat="head in tablehead" >{{head.title}}</th>
  </tr>
</thead>
<tbody>
  <tr ng-repeat="item in items | filter:filterStr">
    <td><a href="#/edit/{{item.id}}">{{item.title}}</a></td>
    <td>{{item.category}}</td>
    <td>{{item.answerer}}</td>
    <td>{{item.author}}</td>
    <td>{{item.created}}</td>
    <td>{{item.answered}}</td>
    <td><span class="disable-item" style="color:{{['red','green'][+item.shown]}};" ng-click="disableItem()">{{['выкл','вкл'][+item.shown]}}</span></td>
  </tr>
</tbody>
</table>
</div>

Здесь следует обратить внимание на конструкцию ['выкл','вкл'][+item.shown] – она подставляет строку из массива ['выкл','вкл'], в зависимости от значения item.shown (0 или 1), унарная операция «+» возвращает число – индекс массива. Пришлось записывать выбор нужной строки таким образом, так как AngularJS не позволяет использовать тренарный условный оператор (item.shown>0? 'вкл':'выкл') в фигурных скобках. Вместо выражения с массивом можно использовать выражение item.shown>0&&'вкл'||'выкл'. Надеюсь, в будущих версиях создатели добавят поддержку тренарных операторов. Конструкция item in items | filter:filterStr передаёт массив items во встроенную функцию filter, которая фильтрует переданные данные, возвращая только те элементы, в которых присутствует подстрока из переменной filterStr (определяемая элементом с атрибутом ng-model=«filterStr»).

Перейдём к контроллеру controllers.js:
'use strict';

function ListCtrl($scope, Items, Data) {
  $scope.items = Items.query(function(data){
    var i = 0;
    angular.forEach(data, function(v,k) { data[k]._id = i++; });
  });
  $scope.categories = Data('categories');
  $scope.answerers  = Data('answerers');

  $scope.tablehead = [
    {name:'title',    title:"Заголовок"},
    {name:'category', title:"Категория"},
    {name:'answerer', title:"Кому задан"},
    {name:'author',   title:"Автор"},
    {name:'created',  title:"Задан"},
    {name:'answered', title:"Отвечен"},
    {name:'shown',    title:"Опубликован"}
  ];

  $scope.disableItem = function() {
    var item = this.item;
    Items.toggle({id:item.id}, function() { if (data.ok) item.shown = item.shown>0?0:1; });
  };
}

В данной функции параметры: $scope – область видимости переменных в шаблоне, используемых в скобках {{}} и в директиве ng-model, Items и Data – сервисы, определённые в файле services.js. Соответственно, Items – модель вопросов, Data – инструмент для получения служебных списков (категории вопросов и отвечающие). $scope – переменная, склеивающая контроллер и вид. Нельзя передавать данные из контроллера в вид иначе, чем через эту переменную (иногда это даже раздражает). Массив tablehead содержит описывающие заголовок таблицы объекты. Позже мы его расширим.

Рассмотрим теперь файл services.js:
'use strict';

angular.module('admin.services', ['ngResource'])
  .factory('Items', function($resource){
    return $resource('back/questions/:id/:action', {}, {
      create: {method:'PUT'},
      saveData: {method:'POST'},
      toggle: {method:'GET', params:{action:'toggle'}}
    });
  })
  .factory('Data', function($resource){
    var load = $resource('back/list/:name', {});
    var loadList = ['answerers','categories'];
    var data = {};
    for (var i=0; i<loadList.length; i++)
      data[loadList[i]] = load.get({name:loadList[i]});
    return function(key){ return data[key]; };
  });

В данном файле используется функция factory(), в данном случае являющаяся генератором ресурсов. Ресурс $resource – встроенный объект, инкапсулирующий работу с XMLHttpRequest. Он содержит дефолтные методы get(), save(), delete() и даёт возможность определить свои методы. По сути, возвращаемые фабриками объекты являются моделью данных. Служба Items подгружает данные с сервера каждый раз при обращении. Служба Data при загрузке страницы кеширует загруженные списки и выдаёт их из кэша по мере запросов.

В принципе, то, что уже есть, будет обеспечивать работу списка, но есть существенные недостатки, которые мы устраним позже. Сейчас же перейдём к странице создания и редактирования вопроса.

Добавление и редактирование записей


Шаблон /views/edit.html достаточно тривиален (по крайней мере для тех, кто знаком с css-фреймворком Bootstrap):

<form name="saveForm" class="form-horizontal">
  <fieldset>
    <div class="control-group">
      <div class="controls">
        <h3>{{["Добавление","Изменение"][(item.id>0)+0]}} записи</h3>
      </div>
    </div>
    <div class="control-group" ng-class="{error: saveForm.category.$invalid}">
      <label class="control-label" for="category">Категория</label>
      <div class="controls">
        <select name="category" ng-model="item.category" required
          ng-options="key as value for (key, value) in categories"></select>
      </div>
    </div>
    <div class="control-group" ng-class="{error: saveForm.title.$invalid}">
      <label class="control-label" for="title">Заголовок</label>
      <div class="controls">
        <input name="title" ng-model="item.title" required>
      </div>
    </div>
    <div class="control-group" ng-class="{error: saveForm.author.$invalid}">
      <label class="control-label" for="author">Автор</label>
      <div class="controls">
        <input name="author" ng-model="item.author" required>
      </div>
    </div>
    <div class="control-group" ng-class="{error: saveForm.answerer.$invalid}">
      <label class="control-label" for="answerer">Кому задан</label>
      <div class="controls">
        <select name="answerer" ng-model="item.answerer" required
          ng-options="key as value for (key, value) in answerers"></select>
      </div>
    </div>
    <div class="control-group" ng-class="{error: saveForm.answerer.$invalid}">
      <label class="control-label" for="text">Текст</label>
      <div class="controls">
        <textarea id="text" ng-model="item.text" required></textarea>
      </div>
    </div>
    <div class="control-group">
      <label class="control-label" for="answer">Ответ</label>
      <div class="controls">
        <textarea id="answer" ng-model="item.answer"></textarea>
      </div>
    </div>
    <div class="form-actions">
      <input type="button" ng-disabled="saveForm.$invalid||saveForm.$pristine" href="#/list" ng-click="save()" class="btn btn-success" value="Сохранить">
      <a href="#/list" class="btn">Отмена</a>
    </div>
  </fieldset>
</form>


В этом шаблоне несколько интересных моментов. Директива, создающая опции списка <select> из объекта записывается так: ng-options=«key as value for (key, value) in categories». Часть после for относится к источнику, выражение до for определяет, какое значение использовать в качестве атрибута value опции, а какое в качестве текста опции.

Директива ng-class="{error: saveForm.title.$invalid}" выставляет тегу класс error при saveForm.title.$invalid == true. Вообще, здесь используется объект, ключами которого являются имена классов, которые установятся в случае, если его значение будет истиной. На кнопке «Сохранить» используется подобная директива ng-disabled=«saveForm.$invalid||saveForm.$pristine», которая устанавливает атрибут disabled кнопке в случае выполнения условия, в данном случае – если в форме есть неверные атрибуты (saveForm.$invalid) или форма ещё не была изменена (saveForm.$pristine). Надеюсь, внимательный читатель догадается о назначении выражения <h3>{{[«Добавление»,«Изменение»][(item.id>0)+0]}} записи</h3>…

К этому одному шаблону, как видно из файла app.js, подключается два контроллера, которые нужно разместить в файл controllers.js (можно и в другой, главное, чтобы они были подключены к странице). Вот код контроллеров (файл controllers.js):
...
function EditCtrl($scope, $routeParams, $location, Items, Data) {
  $scope.item = Items.get({id:$routeParams.id});
  $scope.categories = Data('categories');
  $scope.answerers  = Data('answerers');
  $scope.save = function() {
    $scope.item.$save({id:$scope.item.id}, function(){ $location.path('/list'); });
  };
}

function NewCtrl($scope, $location, Items, Data) {
  $scope.item = {id:0,category:'',answerer:'',title:'',text:'',answer:'',author:''};
  $scope.categories = Data('categories');
  $scope.answerers  = Data('answerers');
  $scope.save = function() {
    Items.create($scope.item, function(){ $location.path('/list'); });
  };
}

Оба контроллера очень похожи, используют встроенный провайдер $routeParams для получения данных из адреса страницы (их имена обозначены в роуте в app.js) и функцию $location.path('/list') для перехода на другую страницу. Обратите внимание! В этой функции не надо использовать символ #, а вот в ссылках в атрибуте href его ставить обязательно.

То, что мы уже сделали, можно посмотреть на этой страничке. Но в текущей реализации вместо названия категории выводится её номер. Устраним этот недостаток.

Подстановка данных из списков


Первым делом настроим, чтобы в столбцы Категории и Кому задан выводились данные из полученных с сервера списков. Для этого создадим специальный модуль admin.filters, в котором будем размещать наши фильтры.
Файл filters.js:
'use strict';

angular.module('admin.filters', [])
  .filter('list', function() {
    return function(value,list) {
      return list?list[value]: value;
    };
  })
...

На вход функция получает значение текущего (фильтруемого) элемента и дополнительный параметр, заданный в шаблоне через двоеточие. Для подключения фильтров к приложению, нужно добавить модуль, их содержащий, в список зависимостей приложения (файл app.js):
...
angular.module('admin', ['admin.services','admin.filters'])
...


В шаблон list.html добавим вызов фильтра с параметром – нужным списком:
...
    <td>{{item.category|list:categories}}</td>
    <td>{{item.answerer|list:answerers}}</td>
...

Теперь, если запустить страницу с внесёнными изменениями, можно увидеть, что на месте числовых индексов появились нужные строки, но вот незадача — стандартный фильтр filter в элементе <tr> ничего не знает об этих строках, так как ему на вход даются нефильтрованные нашим новым фильтром данные. Для правильной фильтрации напишем ещё один фильтр, добавив его также в файл filters.js:
...
  .filter('filterEx', function() {
    var find = function(arr,name) {
      for(var i=0; i<arr.length; i++)
        if (arr[i].name==name) return arr[i].list;
    };
    return function(items,tablehead,str) {
      if (!str) return items;
      var result = [], list, ok, regexp = new RegExp(str,'i');
      for (var i in items) {
        ok = false;
        for (var k in items[i])
          if (items[i].hasOwnProperty(k) && k[0]!='$') {
            list = find(tablehead,k);
            if (list && regexp.test(list[items[i][k]])
                     || regexp.test(items[i][k])) {ok = true; break;}
          }
        if (ok) result.push(items[i]);
      }
      return result;
    };
  });

И добавим вызов этого фильтра в шаблон list.html:
  ...
  <tr ng-repeat="item in items | filterEx:tablehead:filterStr">
  ...

Код фильтра достаточно прост, он принимает три параметра – массив строк и две переменные, приведённые в шаблоне – tablehead и строка для поиска. Затем в цикле перебирает все элементы массива и все ключи в записи, и проверяет через регулярку наличие искомой строки во всех элементах записи, причём, если для элемента в массиве tablehead задан список, то используется значение из него. Также необходимо не забыть внести изменения в массив tablehead, добавив ключ list с массивом строк к нужным элементам (файл controllers.js):
...
  $scope.tablehead = [
    {name:'title',    title:"Заголовок"},
    {name:'category', title:"Категория",  list:$scope.categories},
    {name:'answerer', title:"Кому задан", list:$scope.answerers},
...

На этом базовая часть закончилась, приложение уже достаточно функционально. Разработку бэкенда оставлю за скобками, там всё достаточно тривиально.

Таким образом, мы рассмотрели создание базовых функций административной странички с помощью фреймворка AngularJs. За пределами статьи осталась сортировка таблицы и разбитие на страницы. Об этом я хотел написать следующую статью, если уважаемое хабрасообщество поддержит моё желание.

Рабочее демо доступно здесь: http://lexxpavlov.com/ng-admin/v1/ (read-only)
Исходники можно посмотреть на GitHub: https://github.com/lexxpavlov/angular-admin

Вторая часть
Алексей Павлов @lexxpavlov
карма
11,0
рейтинг 0,0
Программист
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (28)

  • +1
    Вот правильная ссылка на github: github.com/lexxpavlov/angular-admin
    • 0
      упс! спасибо, поправил
  • 0
    > жалко, что не !#, который Гугл принял за стандарт для индексирования
    Как-то немного странно получилось. Есть разные режимы работы: Hashbang and HTML5 Modes. Из документации следует, что по-умолчанию и должно быть '#!'. Поэкспериментировать надо.
    • 0
      Добрался до исходников. Как и предполагалось, в config надо было просто прописать $locationProvider.hashPrefix('!');

      В результате получаем урлы: localhost:8080/#!/list
      • 0
        Верно, спасибо за дополнение. В данном примере нужно в файл app.js:
        angular.module('admin', ['admin.services','admin.filters'])
          .config(['$routeProvider','$locationProvider', function($routeProvider,$locationProvider) {
            $routeProvider /* .when() ... */;
            $locationProvider.hashPrefix('!');
          },
        ]);
        

        При добавлении префикса не забыть поменять адреса в ссылках, и необходимо настроить сервер, чтобы отдавал поисковикам HTML (см. эту статью)
        • 0
          Простите, я не понял. Если раньше сервер отдавал просто json, то сейчас он должен отдать html, но как тогда скоуп получит данные? два запроса, или как?
          • 0
            Конкретно настройка $locationProvider.hashPrefix('!'); приводит к тому, что в адресной строке после хэша появляется ещё и восклицательный знак:
            Было: example.com/#/page-1
            Стало: example.com/#!/page-1
            На саму страницу это никак не влияет, просто добавляется "!". Но вот гугл, видя не просто "#", а "#!" поймёт, что это ссылка на страницу, получаемую по ajax, и сделает запрос на сервер example.com/?_escaped_fragment_=/page-1, где и получит html страницы (а не json).
            • 0
              То есть получается ту страницу (те данные) которые он получит должны быть сгенерирована сервером, точнее php, без angular?
              То есть в блоке ng-view всё делать с помощью php, если смотреть здесь, то там php генерит страницу? Если нет, то как мне в генерируемый html отправить данные в виде объекта, чтобы с отрендерить вид ангуляром. Получается что шаблон template: '/views/list.html' ни как не участвует, ведь в нём есть цикл перебирающий массив, который получал json, а теперь php. Я вообще запутался.
              • 0
                вы правильно понимаете. Гугл по указанному адресу будет ожидать html, и сервер должен подготовить html, вставив туда данные, заполнив списки и прочее. То есть, отчасти, для правильного индексирования ajax-сайта, вся работа по формированию html должна проводиться два раза — angular-ом на клиенте в браузере, и отдельно серверным движком — а будет ли там php, или что-то ещё, не важно. Поэтому многие полюбили Node.js — это серверный javascript, и для него много кода будет общим между клиентом и сервером.

                Я слышал мнение, что сейчас гугл умеет разбирать javascript на страницах, выполнять его и делать ajax-запросы самостоятельно. Но лично я таких данных не имею, и лично не проверял и не читал про это ничего, и если так, будет ли он ещё и js-шаблоны использовать, я не знаю. К тому же, даже если гугл и научился, то насчёт других поисковиков сомнения в таких талантах гораздо больше.
                • 0
                  А как же на счёт инлайновых php вставок в код angularjs? ну типа ng-click=«addCart(<?=$data->id?>)», ведь такой подход не приветствуется.
                  • 0
                    Нет, так делать не надо, конечно же. Потом сами замучаетесь поддерживать подобный код.

                    Если вы хотите сделать индексацию своего ajax-сайта, который работает на клиенте, а с сервера отдаёт только json-данные, то вы должны дополнительно к этому уже готовому сайту отдельно сделать возможность генерировать тот же контент в виде html. Да, генерировать html на сервере. Можно рядом с backend-проектом, который отдаёт json frontend-у, сделать отдельный проект обычного сайта. В случае использования php на сервере это может быть единственным вариантом.

                    Как указано в комментариях статьи, что вы упомянули, указана возможность подключения PhantomJs, который на сервере сам будет запускать ваш js-сайт и возвращать готовый html. Можно на backend перейти на Node.js, и там будет сделать всё проще, чем с php. Там же в комментах был упомянут интересный проект Prerender.io (или AjaxSnapshots.com), который будет за вас делать html с вашего сайта.
                    • 0
                      Где бы глянуть пример на php серверной части. А то сейчас запуская сайт всё хорошо по адресу /cabinet/login, и этот url контролирует AngularJS, и все переходы тоже, но как только я нажимаю обновить страницу, этот URL уже уходит под контроль сервера, а у него по этому адресу пусто, и возвращается ошибка. Не могу понять что я пропустил, может нужно nginx как-то настроить rewrite
  • –2
    Почему-то каждый раз в таких случаях хочется взять Ext и не париться…
  • 0
    Ого. Забавно я сейчас тоже начал переписывать свои админки под Angular. Очень удобно получается. Тут я в первую очередь говорю об админке где ограниченное число администраторов, удобство в том что можно легко контролировтаь используемые технологии, что позволяет использовать последние технологии по полной.
  • 0
    Простите за мою желчь и оффтоп.
    Под удобством админки гораздо чаще подразумевается редактирование страницы (объекта) 1 кликом, вставка картинки одним кликом, и сортировка (смена местами элементов на сайте) перетаскиванием, а не вводом значений для сортировки.
    • 0
      Согласен с вами. Но это уже следующий шаг. Вспомните админки лет пять назад, до ajax-а. Работать можно было, но было дольше и менее удобно. К тому же функция редактирования на самой странице не заменит список всех материалов, иногда нужно видеть всё вместе, и именно с функцией сортировки (о которой в следующей статье, уже скоро).
      • 0
        Пользую в админках jqGrid для подобных целей. Серверная часть выдает JSON, на клиенте надо расписать конфиг, остальное делает jqGrid. Он многое умеет делать из коробки.
        • 0
          Интересный компонент, спасибо, я его не видел. Но, насколько я понял, он всю обработку делает на стороне сервера — страницы, сортировка, поиск. А в моей реализации все данные загружаются на страницу и обрабатываются уже на клиенте. В этом есть и преимущества, и недостатки — зависит от задачи.
          Но в AngularJS можно и постоянную подгрузку данных реализовать, достаточно изменить сервис Items и добавить немного логики в контроллер. Это может понадобиться при огромных таблицах на сотни страниц. Если интересно, могу рассмотреть такой способ работы с данными.
          • 0
            Это может понадобиться в случае, если параллельно несколько человек работает над одной таблицей, и нужно учитывать последние изменения.
            Хотя лучше периодически запрашивать сервер, нет ли изменений в таблице, и только при их наличии запрашивать обновление конкретных записей.
            • 0
              Если делать все по уму, то нужно чтобы сервер стучался ко всем активным клиентам, работающим с конкретной таблицей в данный момент, и если в таблице произошли изменения — то оповещал об этом.

              По большему счету, если не происходит конфликт записей, т.е. никто не пытается одновременно менять одну и ту же запись, то изменения не существенны.
          • 0
            trirand.com/blog/jqgrid/jqgrid.html — тут демки. Вообще jqGrid много чего умеет. :)
  • 0
    С помощью вашей статьи осилил таки начать изучение Angular. Большое спасибо, пишите еще.
  • 0
    Почему вы подключаете отдельные модули для фильтрации и сервисов

    angular.module('admin', ['admin.services','admin.filters'])

    а не настриваете .filter(...) и .factory(...) в самом модуле admin?
    • 0
      Для соблюдения модульности я думаю.
  • 0
    Сейчас из рабочих — ссылка на ГИТ. Каким образом можно просмотреть результат работы? Скачал исходник с ГИТ, но с запуском проблемы. Сори, я новичок в этом.
    • 0
      Да, сейчас мой сайт, на котором была демка, отключен. Попробую выложить на Github Pages. Кину сюда ссылку, как сделаю.
      Вообще, нужно взять содержимое папки app/ репозитория и положить его в корень сайта (или в подпапку), для этого нужно иметь запущенный веб-сервер. Например, если установлен Денвер, то нужно содержимое папки app положить в z:\home\ng-admin\www\ и запускать его в браузере по адресу ng-admin/ (после рестарта денвера, конечно же).
      • 0
        — А если воспользоваться сервером на node.js? В исходниках есть в разделе файл scripts/web-server.js. Его можно использовать?
        — Может подскажешь источники по Angular на русском для начинающих?
        • 0
          На самом деле, Angular — это браузерный javascript, для его работы не нужен веб-сервер. Только вот браузеры ограничивают загрузку файлов шаблонов с диска по своей политике безопасности — при запуске страницы непосредственно с диска не работает ajax. Поэтому и нужен веб-сервер.
          Ну и в моей реализации бэкенд (папка back) сделан на php, поэтому нужно использовать веб-сервер, поддерживающий php. Но его можно очень легко переделать на Node.js (вот, например, из последних статей на хабре по теме — habrahabr.ru/post/213931/). Либо вместо php-бэкенда отдавать готовый json с данными, но тогда добавление/изменение записей не будет работать.
          Перевод официальной документации можно найти на angular.ru, кое-что ещё можно найти вот тут: stepansuvorov.com/blog/2012/12/%D1%81-%D1%87%D0%B5%D0%B3%D0%BE-%D0%BD%D0%B0%D1%87%D0%B0%D1%82%D1%8C-%D0%B8%D0%B7%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-angularjs/

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