Pull to refresh

Comments 45

Спасибо за статью, тема действительно интересна!
Кстати, angular очень хорошо подходит для написания сложных браузерных плагинов.
Документация, в самом деле, никуда не годна. Приходится собирать информацию по крупицам.
Задолбал всех, наверное, со своей рекламой, но, пожалуйста, перевод документации: angular.ru Скоро добью API. Переводите учебник, допиливайте существующие переводы, присылайте свои примеры: angular.ru/cookbook/. Я, когда разберусь с какой-нибудь штукой трачу 30 мин. и пишу пример. Полнота документации зависит в том числе от вас.
Обязательно пишите еще.
По документации на самом деле быстро вникнуть и проникнуться сложновато (особенно после нескольких лет работы с jQuery и ей подобными).
На документацию по AngularJS грех жаловаться. На официальном сайте её много. От туториалов, до продвинутых сценариев с примерами. Одни юнит тесты чего стоят! Если нужно что-то большее — есть поисковики и исходники.
Вот чего действительно не хватает, так это альтернативы директивам. Простенькие или многостраничные сайты на директивах нормально пишутся, но если заходит речь о динамических single-page приложениях, то с директивами тут трешак начинается. Очень неудобно с ними работать, особенно после jquery виджетов.

Почему с директивами неудобно получается? Я сейчас в свободное время изучаю потихоньку ангуляр, и именно директивы кажутся мне очень удобными.
Мне очень нравится модульность Angular'a. В рамках же single-page приложения очень хочется избежать постоянной подгрузки темплейтов с сервера. А ввиду большого количества темплейтов, крайне не хочется хранить все темплейты в одной куче в рутовом index.html, а перенести их в конкретные модули. С простыми темплейтами нет проблем, но как всегда хочется большего. Нужна интернализация, автоматическая подстановка значений из разных scope'ов, быстрая динамическая перестройка html. Первого в angular'е нет, но можно самому написать через директивы; подстановка работает с некоторыми ограничениями; а вот динамически менять dom достаточно муторно. По сути, нужно каждый раз компилировать темплейты, чтобы работали подстановки и выполнялись директивы. Наверно тут нужно определенное мастерство, т.к. то же самое на jquery виджетах делается в 10 раз быстрее и занимает меньше кода. В идеале, возможно было бы здорово совместить jquery виджеты (UI) с angular'овскими модулями (logic), но что-то по-простому это не сращивается.

Не знаю что бы такого в качестве примера привести… За полгода уже подзабылось малость. Ниже идет простенькая директива с кнопками OpenID провайдеров. По сути, приходится темплейты описывать конкатенацией строк, т.е. все описывается декларативно. Если нужно динамически менять какой-нибудь css аттрибут — в темплейт добавляется директива. Если нужен action — добавляется директива. Нужна интернализация — добаляется директива. Форматирование? — директива итд. Так код очень быстро сильно захламляется ненужным синтаксисом. Если же нужно вставить новый блок с другим виджетом динамически… Тут уже траблы. В общем, чем больше виджеты, тем сложнее с ними работать ;)

Если кто подскажет как правильно и эффективно работать с директивами — буду только рад.

angular.module('sipSsoLogin.directive', [ 'sipCommonI18n', 'sipSsoLogin.controller' ]).directive('sipSsoLogin', function () {

    var buttonTemplate = '<button id="sipSsoProvider_%PROVIDER%" ng-click="onProviderSelected(\'%PROVIDER%\')"><div class="sipSsoProvider sipSsoProvider_%PROVIDER%"></div></button>';

    function getProviderButtonTemplate(provider) {
        return buttonTemplate.split("%PROVIDER%").join(provider);
    };

    return {
        restrict: 'E',
        template:
            '<div ng-controller="sipSsoLogin">' +

                '<div class="sipSsoPanel_text" sip-common-i18n="sso.login.directive" />' +
                '<div>' +
                getProviderButtonTemplate('google') +
                getProviderButtonTemplate('yahoo') +
                getProviderButtonTemplate('yandex') +
                getProviderButtonTemplate('twitter') +
                '</div><div>' +
                getProviderButtonTemplate('openid') +
                getProviderButtonTemplate('myopenid') +
                getProviderButtonTemplate('facebook') +
                getProviderButtonTemplate('vkontakte') +
                '</div><div class="sipSsoPanel_inputContainer">' +

                '<button style="float: right; margin: 0px 10px 0px 10px;" sip-common-i18n="sso.button.login"></button>' +
                '<div style="overflow: hidden;" class="sipSsoPanel_inputField">' +
                '<span style="float: left; padding-right: 5px; vertical-align: middle;">{{prefix}}</span>' +
                '<div style="overflow: hidden">' +
                '<input style="display: inline-block; width: 100%;" type="text" value="{{user}}">' +
                '</div>' +
                '</div>' +

                '</div>',
        replace: true
    };

});

Жесть. Надеюсь, что эта куча говно-кода этот пример является просто примером:
1. Необходимо всю разметку перенести в отдельный файл-шаблон и заменить опцию template на templateUrl.
2. Удалить функцию getProviderButtonTemplate и воспользоваться директивой ангуляра ng-repeat
3. Убрать inline-стили. Но это так — общий совет.

Получится что-то вроде такого:
код:
angular.module('sipSsoLogin.directive', [ 'sipCommonI18n', 'sipSsoLogin.controller' ]).directive('sipSsoLogin', function () {
    return {
        restrict: 'E',
        templateUrl: 'loginPanel.html',
        replace: true
    };
});


loginPanel.html
<div ng-controller="sipSsoLogin">
	<div class="sipSsoPanel_text" sip-common-i18n="sso.login.directive" />
	<div ng-repeat="prov in providers">
		<button id="{{prov.sipSsoProviderId" ng-click="onProviderSelected(prov)">
			<div ng-class="prov.sipSsoProvider"></div>
		</button>
	</div>
	<div class="sipSsoPanel_inputContainer">
		<button class="..." sip-common-i18n="sso.button.login"></button>
		<div class="..." class="sipSsoPanel_inputField">
			<span class="...">{{prefix}}</span>
			<div class="...">
				<input type="text" value="{{user}}">
			</div>
		</div>
	</div>
</div>


С этим уже намного приятнее работать и можно двигаться дальше:
4. Директива 'sipSsoLogin.directive' — не нужна вообще (у неё нет функционала кроме замены шаблона). Удалить её и перейти на директиву ng-include которая реализует подобный функционал.

<div ng-include="'loginPanel.html'"></div>

5.… у нас остаётся только разметка, в которой кстати я вижу кастомную интернациализацию. В Ангуляре есть собственные средства интернационализации. Необходимо ими воспользоваться и, подозреваю, что уйдёт ещё куча говно кода
Не беспокойся, это в качестве примера. Inline стили тоже для дебага были. Потом все стили плавно переползают в less.

Вот чего хочется избежать, так это:
1. Необходимо всю разметку перенести в отдельный файл-шаблон и заменить опцию template на templateUrl.


Темплейтов куча. Делать кучу запросов к серверу за темплейтами не хочется. Держать темплейты в одном месте тоже. Как быть?

Интернационализации в angular'е нет.
Все шаблоны должны быть в отдельных файлах. Не хочу даже обсуждать это. Откуда-то с Хабра: (с) Мы пишем код для людей, а не машин.

По поводу требования о загрузке шаблонов. Оно разумно и мне вполне понятно. Смею предложить следующий алгоритм:
1. При сборке проекта все файлы шаблонов объединяются в один (и минимизируются для оптимизации).
2. Файл с шаблонами загружается в главном контроллёре, скажем так:
<body ng-controller="mainController">
    ...
</body>


Контроллёр загружает файл с шаблонами, парсит его (разбивает на составные части) и «накачивает» ими Ангуляровский $templateCache, который использует директива ng-include (необходимо только выяснить в коде ангуляра, код кэша).

Таким образом и код будет выглядеть стандартно и подгрузки шаблонов не будет.
Спасибо, что то похожее думалось сделать, но времени не хватило. Попробую еще раз взяться за angular при первой возможности.
можно использовать grunt-angular-templates для сборки темплейтов в один пекедж — и не надо ничего подгружать на ходу
Вероятно я Вас не правильно понимаю насчёт интернационализации, но разве это не оно? I18n and L10n in AngularJS
Базового функционала явно не хватает. Поэтому народ жалуется на i18n/l10n и пишет свои сервисы, модули итд.
Не копался еще в исходниках. Интересует смена языков. Есть возможность подгружать переводы аяксом, а так же при необходимости не заменять старые переводы? Т.е. чтобы сразу оригинал и перевод показывались?
загрузку ajax-ом не делал — придерживаюсь подхода, что приложение должно быть упаковано целиком, для доставки на клиент. В целом ничего не мешает сделать загрузку через $http — модуль к этому готов.
Отображать одновременно строки из нескольких переводов? Технически возможно, но зачем?
Для админки. Обычные пользователи всегда получают только данные для одного языка, админы и переводчики сразу все, чтобы в редакторе можно было сопоставлять оригинальный текст переводу.

Не очень понятен подход про упаковку. Т.е. для обычных пользователей должны подгружаться интерфейсы админки?
Такого функционала нет и он не нужен в составе основной библиотеки, потому что это уже не локализация.
Можно написать сервис l10n-manage который позволит получить доступ ко всем переводам одновременно и работать с ними как с данными.
Возможно вы и правы, поковыряю вашу библиотечку на досуге, посмотрю как это сделать
Пушнул метод l10n.getAllLocales — получить все загруженные сообщения одним объектом
О! Спасибо! Скоро буду делать многоязычность на сайте, надеюсь, не против, если буду обращаться за советом
Конечно, только лучше через github — чтобы оно потом и другим пригодилось
У рельсового sprockets есть такая удобная фича как JST, смысл в том что все темплейты собираются в глобальной переменной и их удобно вытаскивать в любом месте, например
template: JST[«test/template»]().
Я для этого написал пару директив аля jst-include, в итоге все нужные темплейты загружаются 1 раз, и при этом не представляют собой одностраничной мешанины.
Посмотрите, может в Вашем случае тоже есть подобное решение.
А как мне сделать в роутере для JST, например?
Легко:
when('/main', {template: JST['main'], controller: MainCtrl}).
Спасибо, разобрался. Только window.JST['template'].
Ангулар кеширует вьюшки, причем можно собрать все вьюшки в один js файл и сразу закинуть их в кэш (для автоматизации этого процесса есть плагин grunt-angular-templates).
Для рельсы можешь пример написать?
Мне кажется что директивы как раз таки снимают проблему масштабирования приложений. У меня во всех проектах все разделено на модули, большую часть которых можно реюзать на других проектах. Модули включают в себя сервисы, директивы… От их количества мне ни холодно ни жарко, ибо они никак друг с другом не связаны и используются только там где надо.
А какие проблемы с директивами, какой трешак? Ну, работы чуть больше выходит, да, но вот серьезных проблем при полностью правильном подходе не обнаруживал. Ну и да, ангуляр-то предназначен как раз для сингл-пейдж приоложений, не для простеньких сайтов.
Наверное имеются ввиду проблемы совмещения директив и jquery-плагинов.
Т.к. сами по себе директивы — это просто сказка
Кстати, разработчики честно пишут, что AngularJS хорош именно для CRUD-приложений. Игры или насыщенные DOM-манипуляциями приложения предлагают писать на jQuery. А преимущество директив мне видится в том, что они в разы повышают модульность, поэтому подключать сторонние плагины не сложнее чем в jQuery: $(elem).jqplugin, <div angular-plugin></div>
по поводу такой записи:

function forExampleController($scope) { $scope.word="Habrahabra"; $scope.log=""; }

Такие конструкции любят съедать минификаторы (уже набил шишки:)). Лучше писать так:

angular.module('app').controller('SomeCtrl', ['$scope', function ($scope) { ...

или прописывать зависимости через inject:

SomeCtrl.$inject = ['$scope'];
Согласен, со временем я пришел к такого вида конструкции (видимо Java аннотации перед объявлением классов и методов оказали на меня влияние):

(function () {
  angular.module('myModule').directive('myDirective', MyDirective)

  MyDirective.$inject = ['$scope', '$http']
  function MyDirective ($scope, $http) {
    // bla bla bla
  }
})()
Хороший материал, однако считаю, что в любой туториал по AngularJS хорошо бы добавлять ссылку на замечательные скринкасты от John Lindquist egghead.io
Очень много хабрабры, рябит в глазах. Может лучше более осмысленные имена придумать? «Hello world», например. А лучше «Привет, мир», чтобы данные лучше выделялись среди кода на латинице :-)

Так же стоит упомянуть вначале, что attrs.habraHabr преобразуется соответственно из habra-habr, habra:habr и т.д.

Написать строчку комментария про $watch. Для новичков же статья

Непонятно, зачем писать «внутри директивы interpolate "{{}}"», если проще написать: внутри выражения "{{}}"

Почему второй пример записывается так:
compile: function compile(templateElement, templateAttrs) { templateElement.html("<div>{{"+templateAttrs.habraHabrWork+"}}"+templateAttrs.habra+"</div>"); return function (scope, element, attrs) { } }
а не так, в соответствии с приведенным выше расширенным описанием директивы
compile: function compile(temaplateElement, templateAttrs) { templateElement.html("<div>{{"+templateAttrs.habraHabrWork+"}}"+templateAttrs.habra+"</div>"); }, link: function (scope, element, attrs) { }
Не говорю, что это не правильно, просто без объяснений поменяли логику.

Не очень понято с pre и post. Можно постараться доходчивее объяснить.

Сам в сомнениях, но стоит ли переводить link fn как связующую функцию? Пока перевожу как функция линковки, и сам процесс link как линковку. Не совсем по-русски, но связывание уже употребляется при описании дата-биндинга и могут быть разночтения.

За статью, конечно, спасибо. Пишите еще обязательно!
Спасибо за замечания и предложения
преобразуется соответственно из habra-habr, habra:habr и т.д.

Обязательно расскажу про нормализацию атрибутов и форматы задания директив в заключительной части. Опустил это вначале из тех соображений, что это вопросы скорее относятся к стилю оформления кода, а не к самой концепции директив, и в самом начале особой пользы для понимания в них я не увидел.
Написать строчку комментария про $watch. Для новичков же статья

Да, тоже хотелось уйти от этого, но лучшей альтернативы для того, чтобы показать, как можно при инициализации директивы передавать в нее имя переменной в scope контролера и использовать ее, я не придумал.
Непонятно, зачем писать «внутри директивы interpolate "{{}}"», если проще написать: внутри выражения "{{}}"

Хотел акцентировать внимание на том, что все «выражения», которые пишутся в разметке есть директивы. Возможно слишком назойливо.
Почему второй пример записывается так:
compile: function compile(templateElement, templateAttrs) { templateElement.html("{{"+templateAttrs.habraHabrWork+"}}"+templateAttrs.habra+""); return function (scope, element, attrs) { } }
а не так, в соответствии с приведенным выше расширенным описанием директивы
compile: function compile(temaplateElement, templateAttrs) { templateElement.html("{{"+templateAttrs.habraHabrWork+"}}"+templateAttrs.habra+""); }, link: function (scope, element, attrs) { }

Согласен, внес изменения.

Некоторые вещи лучше вначале не опускать, потому что у человека затык будет, если он сразу не поймет откуда что взялось. Можно хотя бы в скобочках кратко (habraHabr эквивалентен habra-habr из атрибута). Пусть упрощенно, главное, чтобы человек понял что это не ошибка/опечатка/выдумка автора, а так и должно быть и пошел бы дальше.

Про $watch то же самое. Незнакомая непонятная функция, опять затык. А так ($watch следит за изменениями переменных). Можно и подробнее расписать. Хоть целый абзац ввести. Если он будет помогать понять логику, то чтение пойдет быстрее, т.к. не придется задумываться над непонятными вещами.

С "{{}}" тогда уж лучше дописать строчку где отчетливо акцентировать внимание на этом. Например, «… внутри выражения "{{}}" (или внутри любой другой, встроенной в наш элемент директивы)», а то получается ни рыба ни мясо :)
После прочтения статьи, искал ваш комментарий :). Дейсвительно абсолютно не ясно было откуда ангуляр узнал о habra-habr если везде ему указывали habraHabr.
UFO just landed and posted this here
Sign up to leave a comment.

Articles

Change theme settings