Комментарии 24
Реально ли использовать Angular вместе с рендерингом на бекенде?
На mvc я сделал общий view, в котором есть ng-include. Все запросы к любой странице выдают этот view, а он загружает html темплейты в этот ng-include в зависимости от адресной строки. В Angular $location позволяет работать с адресной строкой не перегружая страницу. Такая себе single page app.
Если интересно могу показать код чуть позже, сейчас времени нет.
Если интересно могу показать код чуть позже, сейчас времени нет.
Расценю плюс как интерес.
Вобщем рецепт:
Делаете общий view и добавляете в него подобный код
В контроллере добавляете вот этот код
Суть такова: при загрузке любой страницы, в контроллере сервера вы указываете всегда возвращать один и тот же view. Когда он загружается $watch запускается в первый раз, смотрит на адрес, делает простой роутинг и пишет в contentUrl новое значение, которое подхватывает ng-include и загружает новый контент.
Что-бы перейти на другую страницу делаете так:
$location по умолчанию не перегружает страницу, а только меняет адресную строку. Если браузер не поддерживает такое поведение, то он пишет это как хеш ссылку (address.org#/controller/action) и все тоже работает.
Вобщем рецепт:
Делаете общий view и добавляете в него подобный код
<content ng-controller="ContentTemplate">
<div ng-include="contentUrl"></div>
</content>
В контроллере добавляете вот этот код
$scope.$watch(function () { return $location.path(); }, function (path) {
var match = path.match(/^\/?([^/?]+)(?:\/([^/?]+))?/i);
if (match == null) {
$scope.contentUrl = "Views/Default/Index.html";
return;
}
if (match[2] == null) {
match[2] = "Index";
}
$scope.contentUrl = "/Views/{0}/{1}.html".format(match[1], match[2]);
});
Суть такова: при загрузке любой страницы, в контроллере сервера вы указываете всегда возвращать один и тот же view. Когда он загружается $watch запускается в первый раз, смотрит на адрес, делает простой роутинг и пишет в contentUrl новое значение, которое подхватывает ng-include и загружает новый контент.
Что-бы перейти на другую страницу делаете так:
$location.path("/Controller[/Action]");
$location по умолчанию не перегружает страницу, а только меняет адресную строку. Если браузер не поддерживает такое поведение, то он пишет это как хеш ссылку (address.org#/controller/action) и все тоже работает.
Да можно.
Для подготовки шаблонов можно использовать любой язык и шаблонизатор на Ваш вкус.
Если имелось в виду рендеринг для поисковиков, то тут есть два пути:
Мы пытались пойти по первому пути и долго бились, чтобы все заработало как надо. Мы использовали обертку на node.js, но, к сожалению, сам сервер постоянно падал после нескольких десятков обращений. Поэтому мы переключились на второй вариант, на котором и остановились.
Для подготовки шаблонов можно использовать любой язык и шаблонизатор на Ваш вкус.
Если имелось в виду рендеринг для поисковиков, то тут есть два пути:
- Использовать prerender.io
- Генерить статические странички «на лету» или складировать их заранее в кеш.
Мы пытались пойти по первому пути и долго бились, чтобы все заработало как надо. Мы использовали обертку на node.js, но, к сожалению, сам сервер постоянно падал после нескольких десятков обращений. Поэтому мы переключились на второй вариант, на котором и остановились.
А чем второй путь отличается от первого? Чем вы рендерите во втором способе? Собственно вопрос-то в том как не держать второй комплект шаблонов и т.п. на серверной стороне.
Под капотом у prerender'a находится PhantomJS. Он в real-time сходит на страницу и на выходите даст сформированный html с выполненным javascript'ом.
Во втором случае мы просто сделали, как Вы и упомянули, второй комплект шаблонов, который формировали на сервере из данных БД. На этом и остановились, потому как других путей найти не удалось.
Можно немного расширить второй путь и формировать html'ки заранее, складывая их в какой-нибудь кеш. Но тут все зависит от объема данных и насколько актуальными их надо держать. Нам это не подошло, данные могли меняться быстро, а объем позволял их формировать «на лету» без потери производительности.
Во втором случае мы просто сделали, как Вы и упомянули, второй комплект шаблонов, который формировали на сервере из данных БД. На этом и остановились, потому как других путей найти не удалось.
Можно немного расширить второй путь и формировать html'ки заранее, складывая их в какой-нибудь кеш. Но тут все зависит от объема данных и насколько актуальными их надо держать. Нам это не подошло, данные могли меняться быстро, а объем позволял их формировать «на лету» без потери производительности.
Тем не менее, важно этим не злоупотреблять, потому что зарегистрировать и получить событие можно в каждом контексте ($scope) любого контроллера.
Добавлю что например для глобальных событий вместо использования $broadcast лучше подписывать события на $rootScope и иницииривать на нем же через $rootScope.$emit. И вообще как по мне оыбчно достаточно глобальны событий, иначе может возникнуть лишняя сложность.
Интересно было бы узнать о применении AngularJs для разработки Web-приложений, работающих в браузере мобильных устройств. Как в этом случае трансформируются рекомендуемые ограничения по количеству watch-ей на странице? Было бы здорово еще и узнать о каких-нибудь примерах из реального опыта.
Наше приложение работало также и в мобильных браузерах, и надо отметить это было нашей ошибкой. Вкратце:
Сейчас, мы бы разделили эти приложения на два разных с точки зрения UI части, но объединили бы логическую часть (сервисы) через Ionic. Это было бы правильнее, на мой взгляд.
- Производительность раза в 2-3 (субъективно) хуже, чем на десктопе. У нас использовалась masonry-плитки с картинками.
- Пришлось в одном приложении адаптировать пользовательский интерфейс одновременно и для мобильных устройств, и для десктопа. Это вылилось в большую кучу «костылей» и ветвлений в коде, «резиновая» верстка полностью не покрывала все случаи. Чудес, увы, не бывает.
- Было очень много багов, связанных с конкретными браузерами на устройствах. Поддерживать их все было очень сложно. Бывали даже случаи, когда один и тот же баг в одной и той же версии браузера повторялся на устройствах Samsung, но упорно не хотел на устройствах LG (Nexus)
Сейчас, мы бы разделили эти приложения на два разных с точки зрения UI части, но объединили бы логическую часть (сервисы) через Ionic. Это было бы правильнее, на мой взгляд.
Но ведь эти проблемы связаны не непосредственно с AngularJs, я правильно понимаю? Т.е. понятно, что резиновый интерфейс не всегда позволяет сделать интерфейс удобным для всех форм-факторов, а косяки с разметкой могут быть разные в разных браузерах. Но если все-таки этот путь выбран (хотя бы как временный), то как себя будет вести AngularJs со своим двусторонним связыванием в мобильном браузере? В аспекте производительности и в аспекте совместимости AngularJs с разными браузерами…
Да, Вы правы, эти проблемы напрямую не относятся к AngularJS. Не могу сказать, что фреймворк накладывал какие-то ограничения на использование в мобильных браузерах конкретно у нас.
Сложновато отделить проблемы с производительностью двойного связывания от наведенных проблем другими компонентами. У нас показывалось примерно до 200 «плиток» на странице (может быть и больше удавалось показывать за счет бесконечной прокрутки, сложно сказать) и на каждой порядка 15 элементов были с двойным связыванием. Хуже всех себя показывал нативный Android браузер. Chrome, Safari и Firefox были примерно на одном уровне. Были небольшие тормоза при прокрутке и «затыки» при добавлении новых элементов, вероятно связанные в большей степени из-за манипуляций с расчетом позиций этих «плиток».
В целом, не могу сказать что производительность страдала конкретно из-за AngularJS. При разумном применении (не на синтетическом примере в 2000 $watch), я думаю скорость не сильно упадет.
По совместимости, могу сказать, что не сталкивался с проблемами. Во всех браузерах логический код отрабатывал ожидаемо.
Сложновато отделить проблемы с производительностью двойного связывания от наведенных проблем другими компонентами. У нас показывалось примерно до 200 «плиток» на странице (может быть и больше удавалось показывать за счет бесконечной прокрутки, сложно сказать) и на каждой порядка 15 элементов были с двойным связыванием. Хуже всех себя показывал нативный Android браузер. Chrome, Safari и Firefox были примерно на одном уровне. Были небольшие тормоза при прокрутке и «затыки» при добавлении новых элементов, вероятно связанные в большей степени из-за манипуляций с расчетом позиций этих «плиток».
В целом, не могу сказать что производительность страдала конкретно из-за AngularJS. При разумном применении (не на синтетическом примере в 2000 $watch), я думаю скорость не сильно упадет.
По совместимости, могу сказать, что не сталкивался с проблемами. Во всех браузерах логический код отрабатывал ожидаемо.
Спасибо!
Еще попутно вопрос по структурированию кода приложения: где, по Вашему мнению, лучше размещать логику построения (перестроения) DOM, общую для нескольких директив? Ведь наследования директив нормальными способами не добиться?
Еще попутно вопрос по структурированию кода приложения: где, по Вашему мнению, лучше размещать логику построения (перестроения) DOM, общую для нескольких директив? Ведь наследования директив нормальными способами не добиться?
А можете привести пример такой логики? Не встречал такой, если честно…
Наследования как такового конечно же нет, но есть очень полезная штука require. С ее помощью можно логически объединять директивы, зависящие друг от друга. Например, если Вы захотели сделать собственный dropdown, то можно директиву разбить на две части. Одна часть будет связана с логикой «навешивания» на элемент (и таких директив может быть несколько, в зависимости от элемента, например), а вторая часть будет реализовывать показ непосредственно панели со списком.
Такой подход позволит использовать общую часть (панель со списком) с разными элементами и директивами (меню или инпут с селектом).
Наследования как такового конечно же нет, но есть очень полезная штука require. С ее помощью можно логически объединять директивы, зависящие друг от друга. Например, если Вы захотели сделать собственный dropdown, то можно директиву разбить на две части. Одна часть будет связана с логикой «навешивания» на элемент (и таких директив может быть несколько, в зависимости от элемента, например), а вторая часть будет реализовывать показ непосредственно панели со списком.
Такой подход позволит использовать общую часть (панель со списком) с разными элементами и директивами (меню или инпут с селектом).
Например, я хочу несколько директив, которые по сути своей dropdown-контролы, но содержимое выпадающей области разное: в одном случае простой список, в другом — какая-то более ложная конструкция, позволяющая искать/выбирать элементы. В силу того, что большую часть времени я пишу на языке с наследованием, мне описанное разнообразие хочется сделать с помощью соответствующих классов-наследников. Хотя, require, похоже тоже для этого подойдет.
Насколько Angular подходит для написания приложения с не очень сложной svg графикой в дуэте со Snap.svg, например? Или для подобных приложений он не нужен вовсе?
Избавиться от конструкций вида
поможет ng-annotate: github.com/olov/ng-annotate
['GoogleMapService', function (GoogleMapService) {
поможет ng-annotate: github.com/olov/ng-annotate
На момент старта нашего проекта, такой штуки не было, и поэтому все привыкли описывать зависимости инъекций руками.
Добавлю в полезные инструменты.
Добавлю в полезные инструменты.
В целом такой подход (анонимные функции) объявления модулей вообще не рекоммендуется. Лучше объявлять именованные функции и их зависимости через $inject или ng-annotate (делает тот же $inject). В моем случае все моудли разбиты на отдельные файлы и линкуются через browserify, именем модуля является имя функции, довольно удобно.
В целом такой подход (анонимные функции) объявления модулей вообще не рекоммендуется.
А почему не рекомендуется? Не встречал такого мнения…
В моем случае все моудли разбиты на отдельные файлы и линкуются через browserify, именем модуля является имя функции, довольно удобно.
У Вас в одном файле собраны все сервисы, контроллеры и директивы, связанные с модулем?
Мы разбивали каждую отдельную сущность на отдельный файл. Структура слегка «пухла», но в целом было удобнее вносить точечные правки и рефакторить файл, было меньше конфликтов.
А почему не рекомендуется? Не встречал такого мнения…
Хотя бы потому что анонимную функцию не получится вынести в отдальный файл, что желательно как по мне. Проще читается (особенно когда все разнесено по отдельным файлам). Отлаживать проше и прочее. Здесь еще можно почитать github.com/johnpapa/angular-styleguide
У Вас в одном файле собраны все сервисы, контроллеры и директивы, связанные с модулем?
Да, модуль который просто все связывает и больше ничего не делает. Примерно так:
- 'use strict';
- var dependencies = [];
- var controller = require('./profile-controller');
- var profilePreview = require('./profile-preview-directive');
- // blocks
- var boxDirective = require('./blocks/profile-box-directive');
- var connectLinksDirective = require('./blocks/profile-connect-lInks-directive');
- var exploreDirective = require('./blocks/profile-explore-directive');
- var groupsDirective = require('./blocks/profile-groups-directive');
- var summaryDirective = require('./blocks/profile-summary-directive');
- var skillsDirective = require('./blocks/profile-skills-directive');
- var experienceDirective = require('./blocks/profile-experience-directive');
- var educationDirective = require('./blocks/profile-education-directive');
- module.exports = angular.module('app.profile', dependencies)
- .controller(controller.name, controller)
- .directive(profilePreview.name, profilePreview)
- // blocks
- .directive(boxDirective.name, boxDirective)
- .directive(connectLinksDirective.name, connectLinksDirective)
- .directive(exploreDirective.name, exploreDirective)
- .directive(groupsDirective.name, groupsDirective)
- .directive(skillsDirective.name, skillsDirective)
- .directive(summaryDirective.name, summaryDirective)
- .directive(experienceDirective.name, experienceDirective)
- .directive(educationDirective.name, educationDirective);
Эка…
будет срабатывать каждый раз при переписывании dataLoaded?
Оно же просто добавит handler к тому промизу, на который в данный момент ссылается dataLoaded.
$scope.dataLoaded.then(handler)
будет срабатывать каждый раз при переписывании dataLoaded?
Оно же просто добавит handler к тому промизу, на который в данный момент ссылается dataLoaded.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Опыт разработки и проектирования на AngularJS