Pull to refresh

Comments 36

Вот! Ну вот же именно то, чего мне и не хватало. Спасибо за статью. Все доходчиво расписано.
У второго ангуляра тоже есть multi transcluding https://toddmotto.com/transclusion-in-angular-2-with-ng-content#angular-2-content-projection

Да, но как с их помощью поместить контент в body? В первом ангуляре можно сделать transclude вручную, во втором, похоже, нельзя.

В то же время, у меня так не заработало, а работает import {} from "angular2/core".

А что в package.json? И rm -rf node_modules && npm i не помешает. Импорт из "angular2/что-то" работает только для бета-релизов, а тем временем уже 4-й RC вышел. А так как вы пользуетесь бетой (уже устаревшей), отсюда и могут возникать некоторые проблемы.
Например, разрабы говорили, что в RC подчистили код и его общий размер уменьшился. Если не включать в бандл всю rxjs и прогнать вебпаковским оптимизатором, то размер бандла вполне можно уменьшить до сотен килобайт.


А для динамического размещения компонентов в DOM есть DynamicComponentLoader.

Да, действительно, версия ^2.0.0-beta.17, ставил npm install angular2.


DynamicContentLoader похоже может загружать компоненты в произвольное место документа, но он помечен как deprecated.

В дополнение, вариант (jsfiddle) на Angular Light.
Из функционала мог что-то упустить, но вроде все есть: контроллер + попап и оверлей под него.
Итого 34 строки (можно покороче сделать), вес либы 18кб (сжатый).
Также можно было бы рассмотреть очень компактные альтернативы для Angular это Knockout.js, для React это riotjs.
Очень печально что массово побеждает «монструозность» и большие размеры библиотеки без существенных явных преимуществ.
На клиентской стороне также чрезмерное увлечение препроцессорами, что на самом деле большая дикость и по сути является «костылями».
Любой человек, которому приходится заниматься как серверной так и клиентской частью, замечал насколько более глюкава и странна клиентская часть на Javascript. В этом смысле Knockout / Riotjs максимально прозрачны, ближе к чистому Javascript, что является плюсом.

С KnockoutJS у меня как-то не задались отношения. Мне не очень нравится необходимость подготавливать модели. Ангуляр подкупает своей возможностью, грубо говоря, "редактировать JSON".


Riotjs выглядит интересно.

Дело в том, что редактирование JSON без Object.observe() это некая магия — не всегда понятно как и когда произойдет обновление данных. В Knockout все очевидно — observable является функцией, когда мы вызываем ф-цию с новым значением в качестве аргумента, выполняется код, который обновляет DOM (хотя в современных версиях Knockout отдельная очередь обновлений, не всегда сразу).

Да, я соглашусь что глубоковложенные структуры в Knockout редактировать неудобно, даже с mapping плагинами. Поэтому стараюсь не допускать большой вложенности, чаще всего это не нужно.

Я с KnockoutJs давно не работал, но раньше, помню, постоянно была проблема с взаимозависимостями, да и с обычными тоже, вроде бы понятно, как это работает, но предусмотреть и продумать все случаи в сложных моделях просто нереально. Приходилось менять структуру объекта, выносить какие-то свойства или функции, что ломало нафиг маппинг.


В общем, проще в принципе не значит проще на практике :) Надеюсь, они пофиксили это в новых версиях, возможно, там можно было бы разделить фазы инвалидации зависимостей и их пересчета, чтобы избавиться от множественных каскадных пересчетов.


Ангуляр, конечно, достает своим $digest циклом, он вроде как и не должен использоваться, но постоянно приходится. Но работа самого dirty-checker-а очень предсказуемая, как по мне, обычно все работает.

редактирование JSON без Object.observe() это некая магия, В Knockout все очевидно

Из-за ограничений JS каждый фреймворк имеет свои минусы, сейчас в основном есть несколько путей слежения за данными:

1) Object.observe, (использовался в Aurelia и Angular Light), это правильное направление, но текущая реализация эксперементальная, медленная, есть только в хроме (точнее в вебките), и вообще её автор хотел её вырезать из «ES7».

2) Observable, (Knockout, Ember, frp), там где данные нужно заворачивать либо использовать сеттеры/геттеры, т.е. нет возможности оперировать данными простым JS, нужны всякие враперы и т.п. что вносит сложность, неудобство и тормоза (посмотрите бенчмарки, нокаут и ембер обычно в хвосте, с фрп отдельная история).

3) Dirty checking (Angular.js, Angular Light, React (сюрприз?), происходит просто сравнение всех данных, старого значения с новым, плюс в том что работа с данными идет как есть, нет оберток, нет подмены данных, в итоге работа с данными идет просто и быстро, минус в том что нужно вручную* запускать это сравнение. В случае с React, идет сравнение DOM (virtual DOM), когда Angular сравнивает изначальные данные. Ручное обновление в некоторых местах автоматизировано, где-то обертки ($timeout/$http), в React это начитнается с обновления стейта.
Так же ещё плюс в том что легко и просто отслеживаются «сложные» выражения, например «foo()», вызов функции, в которой может быть что угодно, когда в других подходах нужно вычислять зависимости и делать «compound» объекты, с другой стороны такая функция может будет вызываться часто, поэтому должна быть легкая.

4) Property (vue.js, aurelia, Matreshka.js), суть в том что отслеживаемые данные подменяются пропертями, в некоторых случаях подменяется даже сам объект, минус в том что ваши данные будут «изнасилованы» под фреймворк, что несет свои минусы.

5) Zone.js (Angular2), в корне используется тот же dirty-checking, но что-бы вручную не вызывать синхронизацию, весь мир обкладывается враперами — подменяются setTimeout, setInterval, события элементов, ajax, и т.п. негативно отражается на расход памяти и скорость (хотя возможно не критично, не замерял).

6) Proxy (в использовании фреймворками не замечен), считается как замена Object.observe, хотя принципы совсем другие, принят в ES6 и уже есть в современных браузерах (IE12+ и т.п., но кто откажется от старых браузеров на сегодняшний день...), возможно через несколько лет будет использоваться как основной инструмент для «отслеживания», если не запилят нормальный Object.observe.

Вообщем если «убрать» один минус из фреймворка, появится другой, в итоге вы выбираете какие минусы вас меньше «раздражают».

На самом деле это правильная практика "насиловать" данные, а не прихать ответ от сервера напрямую во вьюшку без какой либо нормализазии. А нокаут и эмбер тормозят не из-за обсервабелов. $mol на том же принципе работает очень шустро: https://github.com/nin-jin/todomvc/blob/master/benchmark

Нормализация — это еще пол-беды. Для нокаута приходится данные копировать, вне зависимости от того, нужны ли изменения структуры, а часто потом еще и менять структуру для того, чтобы нокаут просто работал.

Например?


Знаете, с объектной моделью предметной области куда удобнее работать, чем с голыми данным в json-е.

На самом деле это правильная практика «насиловать» данные
Почему она правильная? Например с сервера прилетают готовые данные, я их беру и отображаю, зачем тут ещё промежуточные обертки, так же если я использую какие-то сторонние либы, возня с обертками не нужна.
$mol на том же принципе работает очень шустро
Вообще да, минимальная версия может быть довольно быстрой, но все равно я считаю, что реализация нокаута медленная. У вас в $mol есть возможность делать compound функции?

Потому что получается сильная связность клиента и сервера. Вместо простого нормализованного API серверу приходится городить огород под клиента. А клиентов много разных и каждому удобно в своём формате. Кроме того, куда вы поместите функции для работы с данными? Во вьюшку? В неймспейс? А если вы обернёте всё это в модели, данные засуните в их свойства и насыпете методов по вкусу, то сможете реализовать удобный и практичный API.


$my_team( 'admin' ).members( [ me , $my_user( 'vasya' ) ] , [] ) // добавить в админы меня и Bасю

me.teams( [ $my_team( 'admin' ) , $my_team( 'editor' ) ] , [] ) // добавить меня в админы и редакторы

$my_team( 'admin' ).membersCount() // возвращает число админов; если список админов не загружен, но сервер отдал их число, то возвращаем его; иначе - делаем запрос к серверу, а потом возвращаем число.

compound — это зависимые от множества наблюдаемых переменных? Да, от любого их числа. причём отслеживается даже неявная зависимость:


class UserGreeter {

    @ $mol_prop()
    nameFirst() { return 'Jin' }

    @ $mol_prop()
    nameLast() { return 'Nin' }

    nameFull() { return `${this.nameFirst()} ${this.nameLast()}` }

    greeting() { return `Hello, ${this.nameFull()}` }

    @ $mol_prop()
    childs() { return [ this.greeting() ] } // тут будет зависимость от this.nameFirst() и this.nameLast()

}
Вместо простого нормализованного API серверу приходится городить огород под клиента.

Да нет же, в том-то и дело, с тем же Angular ничего не нужно городить, у него достаточно гибкости, чтобы обеспечить редактирование нормализованных данных.


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

В сервисы. Если же вы имели ввиду нечто вроде nameFull() — то в контроллер, во вью или в pipe. Взаимодействие с пользователем — в контроллеры.

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


Сервис — это такое модное название неймспейса для процедурок? :-)


Копипастить производные свойства модели по контроллерам и вьюшкам — так себе решение. А кто такие pipe?

причём отслеживается даже неявная зависимость:
т.е. у вас на каждый вызов ф-ии идет трекинг и перестроение зависимостей? (случай с условиями)

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

Да.


Чтобы создать объект не обязательно его "загружать". Он может сам лениво грузить свои данные.

$my_team( 'admin' ).membersCount() // возвращает число админов; если список админов не загружен, но сервер отдал их число, то возвращаем его; иначе — делаем запрос к серверу, а потом возвращаем число.
Если идет запрос к серверу, то что возвращается (0/void 0/promise)? И как вы обрабатываете когда нужен реальный результат (вместо временных 0/void 0)?

Кидается исключение $mol_atom_wait, которое прерывает исполнение текущего "потока". Когда данные придут, исполнение будет перезапущено и функция уже вернёт актуальные данные.


Код получается примерно такой:


clickHandler = event => {
    $mol_atom_task( ()=> {
        const adminTeam = $my_team( 'admin' )
        if( adminTeam.membersCount() < 1 ) {
            const admins = adminTeam.members([ me ])
            alert( 'New admins: ' + admins.map( user => user.name() )join( ', ' ) ) 
        }
    })
}

$mol_atom_task создаёт атом, который дожидается окончания вычисления, а потом самоуничтожается.


Тут больше подробностей об этом: https://habrahabr.ru/post/317360/

В случае с React, идет сравнение DOM (virtual DOM), когда Angular сравнивает изначальные данные.

Насколько я понимаю, React тоже сравнивает изначальные данные в дефолтной реализации shouldComponentUpdate?

Это зависит от компонента, shouldComponentUpdate, видимо, создан для оптимизации, т.к. сравнение vdom медленнее чем сравнение изначальных данных.
Для React есть удобный сторонний компонент — react-portal, с ним реализация всплывающих штук вообще не вызывает трудностей
Да, такая эта техника называется «порталами», но работает она через `ReactDOM.unstable_renderSubtreeIntoContainer`, и я удвилен, почему в статье используется обычный рендер.
Ну и раз уж на то пошло, то лучше взять что-то на хорошей поддержке. Например react-modal от facebook (но там много гвоздями прибито) или react-overlays от bootstrap. Последнее — шикарное исполнение, включающее в себя базовый портал и разные компоненты, построенные на нем.

react-overlays тоже использует ReactDOM.unstable_renderSubtreeIntoContainer. В чем его отличие от обычного render, вы не знаете случайно?

В глаза сразу бросается сохранение контекста (потому и называется «порталом»). Что они там еще придумали, сразу не понятно.

Не совсем понимаю, что вы имеете ввиду под сохранением контекста? Можете пояснить?

Прошел квест по созданию модальных окон во втором ангуляре. Если коротко — задача нетривиальная, особенно если надо реализовать универсальный механизм с минимумом ограничений (не просто статические попапы или алерт боксы) и минимумом копипасты, с подержкой нескольких модальных/немодальных окон, горячих клавиш. Неинтуитивная — это мягко сказано. Я смотрел на исходники https://github.com/shlomiassaf/angular2-modal. Использовать эту либу целиком было страшно (представляю сколько парень похоронил времени), поэтому написал свой модуль (раз в десять меньше кода, проще в поддержке).


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

Насчет модального окна на React.js.
Нет необходимости вставлять компонент Popup в другие компоненты.

Можно сделать класс, который будет содержать ссылку на экземпляр компонента Popup и работать с ним.
Тогда, вместо вставки в код других компонентов, можно будет отобразить/скрыть модальное окно так:
Modal.open(modalBody);
Modal.hide();
но ведь по сути, это будет то же самое, что предложено в статье. только не через рендериг компонента, а через обертку. и, опять же, потеряете контекст (про контекст чуть выше)

Контент попапа может быть динамическим, и зависеть от состояния вызывающего компонента.

С попапами в результате пришел к следующему подходу:
1) Попап — это не компонент, а стейт. Он не является частью текущей страницы.
2) Работа с попапом аналогична работе с удаленным сервисом: открыли с некоторыми параметрами, после закрытия получили результат работы.


Получилось что-то такое:


angular.module('app').config($popupProvider => {
  $popupProvider.popup('login', {
    controller: ($scope) => {
      //в $scope.popup контроллер попапа, в $scope.popup.options - переданные при открытии данные.
      // По идее, лучше бы инжектить, но руки не дошли.
    }, 
    template: `
      <div class='popup__title'>Login</div>
      <div class='popup__content'>...</div>
      <div class='popup__buttons'>
        <button ng-click="ctrl.login()">Login</button>
        <button ng-click="$popup.close(false)">Cancel</button>
      </div>
    `
  });
});

Далее, можем открыть попап в любом контроллере:


$popup.open('login', options)
  .then(result => {

  });

Открывшийся попап вставляется в дно body, каждый раз инстанцируя контроллер. При закрытии дестроится, попап удаляется и DOM.

Это похоже на первый подход для Angular 1, который я описал.


Это может быть удобно при открытии поп-апа императивно — из какой-то логики, для запроса параметров, показа алерта и т.п.
Но иногда удобнее, чтобы поп-ап был частью разметки — например, при написании контролов типа дроп-дауна, либо сложных форм, где часть параметров выносится в поп-ап, но является частью скоупа. Тогда можно применять второй способ.

Sign up to leave a comment.

Articles