image Много раз слышал утверждение, что Angular 2 должен быть особенно хорош в корпоративных приложениях, поскольку он, мол, предлагает все нужные (и не очень) прибамбасы сразу из коробки (Dependency Injection, Routing и т. д.). Что ж, возможно это утверждение имеет под собой основу, поскольку вместо десятков разных библиотек разработчикам надо освоить один только Angular 2, но давайте посмотрим, насколько хорошо базовая (основная) функциональность этого фреймворка годится для корпоративных приложений.

По моему опыту, типовое корпоративное приложение — это сотни (иногда тысячи) практически идентичных страниц, которые лишь слегка отличаются друг от друга. Думаю, не мне одному приходила в голову мысль, что неплохо бы выделить повторяющийся функционал в отдельный компонент, а специфичное поведение определять через параметры и внедряемые шаблоны. Давайте посмотрим, что Angular 2 может нам предложить.


Простой вариант


Первое, что приходит в голову — это отражение содержимого декларации компонента в его DOM c помощью специального тега <ng-content select="...">.

Попробую продемонстрировать этот подход на примере простого компонента «widget».
Сначала пример использования:

  <widget>
    <div class="content">
      <button>
        Just do my job...
      </button>
    </div>
    <div class="settings">
      <select>
        <option selected="true">Faster</option>
        <option>Slower</option>
      </select>
    </div>
  </widget>

И то, как это выглядит:



Внутри компонента «widget» мы определяем два элемента:

  1. Помеченный классом «content» – основное содержимое виджета;
  2. Помеченный классом «settings» – некие настройки, относящиеся к виджету;

Сам компонент «widget» отвечает за:

  • Отрисовку рамки и заголовка;
  • Логику переключения между режимом показа основного содержимого и режимом показа настроек.


Теперь посмотрим на сам компонент “widget”:

@Component({
    selector: "widget",
    template: `
  <style>
    ..
  </style>
  <div class="hdr">
    <span>Some widget</span>
    <button *ngIf="!settingMode" (click)="settingMode = true" class="wid_btn">
      Settings
    </button>
    <button *ngIf="settingMode" (click)="settingMode = false" class="wid_btn">
      Ok
    </button>
  </div>
  <div class="cnt">
    <ng-content *ngIf="!settingMode" select=".content">
    </ng-content>
    <div *ngIf="settingMode">
      Settings:
      <ng-content select=".settings">
      </ng-content>
    </div>
  <div>    
    `})
export class Widget {
  protected settingMode: boolean = false;
}

Обратите внимание на два тега ng-content. Каждый из этих тегов содержит атрибут select c помощью которого происходит поиск элементов, предназначенных для замены оригинальных ng-content. Так, например, при показе нашего виджета:

<ng-content select=".settings" /> будет заменен на .. a <ng-content select=".content"/> на .. Естественно, что поиск элементов ограничен тегом <widget>...</widget> в клиентской разметке. Если замена не была найдена, то мы просто ничего не увидим. Если условию выборки удовлетворяет несколько элементов, то мы увидим их все.

Вариант посложнее


Описанный выше подход может быть успешно применен во многих случаях, когда требуется создать шаблонизируемый компонент, но иногда этого подхода оказывается недостаточно. Например, если нам необходимо, чтобы переданный компоненту шаблон был отображен несколько раз причем в разных контекстах. Для того, чтобы объяснить проблему давайте рассмотрим следующую задачу: в нашем корпоративном приложении есть несколько страниц со списками объектов. Каждая страница отображает объекты какого-то одного типа (пользователи, заказы, что угодно), но при этом каждая страница позволяет вести постраничный просмотр объектов (пейджинг) и имеет возможность отметить некоторые объекты для того, чтобы выполнить некую групповую операцию, например, “удалить”. Хотелось бы иметь компонент, который бы отвечал за пейджинг и выбор элементов, но способ отображения элементов оставался бы ответственностью конкретной страницы, что логично, поскольку отображать пользователей и заказы обычно нужно по-разному. В случае подобной задачи ng-content уже не подходит, так как он просто отображает один объект внутрь другого, но нам нужно не просто отобразить, но еще и расставить галочки напротив каждого объекта (индивидуальный контекст).

Забегая вперед, сразу покажу решение этой задачи на примере компонента “List Navigator”, который я сконфигурировал на отображение информации о пользователях (исходники здесь).

<list-navigator [dataSource]="dataSource" [(selectedItems)]="selectedUsers">
  <div *list-navigator-item="let i, let isSelected = selected" 
    class="item-container" 
    [class.item-container-selected]="isSelected"
  >
    <div class="item-header">{{i.firstName}} {{i.lastName}}</div>
    <div class="item-details">
        Id: {{i.id}}, 
        Email: {{i.email}}, 
        Gender: 
        <span [ngClass]="'item-details-gender-'+ i.gender.toLowerCase()">
            {{i.gender}}
        </span>
    </div>
  </div>
</list-navigator>



Идея следующая: компонент в качестве параметра получает ссылку на функцию, возвращающую диапазон объектов по смещению и размеру страницы (offset и pageSize):

[dataSource]="dataSource"

this.dataSource = (o, p) => this._data.slice(o, o + p);

а также шаблон, описывающий как необходимо отображать эти объекты:

<div *list-navigator-item="let i, let isSelected = selected"…

Аргумент *list-navigator-item – это своего рода маркер, который позволяет нашему компоненту понять, что элемент, им помеченный, является шаблоном (символ ‘*’ говорит ангуляру, что перед нами не просто элемент, а именно шаблон) который должен быть использован для отрисовки очередного объекта из диапазона, возвращаемого dataSource. С помощью list-navigator-item мы также задаем две переменные:

  • let i – ссылка на очередной объект из диапазона;
  • let isSelected = selected – булевское значение указывающие отмечен ли этот элемент галочкой
    или нет (о том, что означает “= selected” мы поговорим позже).

Помимо этого, компонент в качестве параметра получает список “выбранных” элементов (будут обозначены галочкой), и в случае, если пользователь меняет выбор, то компонент возвращает уже обновленный список, соответствующий пользовательскому выбору:

[(selectedItems)]="selectedUsers"

Можно провести следующую аналогию: мы передаем компоненту “фабрику”, которая создает новый элемент интерфейса используя переданные компонентом параметры. Затем наш компонент размещает созданный фабрикой элемент интерфейса внутри себя туда куда нужно (напротив галочки в нашем случае).

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


  • list-navigator-item-outlet — Директива-outlet, которая, собственно, и отвечает за создание нового элемента, используя template и текущий контекст (“фабрика” из вышеприведённой аналогии) (часть внутренней реализации компонента);
  • list-navigator-item-сontext – Класс-контейнер для передачи данных контекста (часть внутренней реализации компонента);
  • list-navigator-item – директива-маркер, с помощью которой компонент получает доступ к шаблону элемента;
  • list-navigator – собственно компонент, который реализует основное поведение, а именно пейджинг и выбор элементов.


list-navigator-item-outlet


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

Совсем небольшая директива, поэтому приведу ее исходный код целиком:

@Directive({
    selector: "list-navigator-item-outlet"
})
export class ListNavigatorItemOutlet
{
    constructor(private readonly _viewContainer: ViewContainerRef){}

    @Input()
    public template: TemplateRef<ListNavigatorItemContext>;

    @Input()
    public context: ListNavigatorItemContext;

    public ngOnChanges(changes: SimpleChanges): void
    {
        const [, tmpl] = analyzeChanges(changes, () => this.template);
        const [, ctx] = analyzeChanges(changes, () => this.context);

        if (tmpl && ctx) {
            this._viewContainer.createEmbeddedView(tmpl, ctx);
        }
    }
}

В конструкторе мы запрашиваем у Angular 2 объект типа ViewContainerRef. С помощью этого объекта мы можем управлять визуальными элементами (View) (не путать с элементами DOM браузера) в родительском компоненте. Среди всех возможностей ViewContainerRef нас в данный момент интересует возможность создания новых визуальных элементов, это можно сделать с помощью следующих двух методов:

  • createComponent(componentFactory: ComponentFactory<C>,...)
  • createEmbeddedView(templateRef: TemplateRef<C>, context?: C,...)

Первый метод полезен в том случае, если мы хотим динамически создать компонент, имея на руках лишь его “класс”. Этим методом пользуется, например, ангуляровский роутер, но это тема заслуживает отдельного поста (если, конечно, будет интересно). Сейчас же давайте обратим внимание на второй метод createEmbeddedView, с помощью которого мы и создаем наших пользователей. В качестве параметров он принимает TemplateRef и некий контекст.

TemplateRef – это “фабрика” по созданию новых визуальных компонентов, полученная путем “компиляции” тега <template> в макете компонента (тот, который template: ‘…’ или templateUrl: “…”). Но ведь до сих пор мы нигде не видели этот тег, откуда же тогда взялся TemplateRef? На самом деле у нас был тег <template>, просто мы его определили неявно, использовав синтаксический сахар в виде символа ‘*’:

<div *list-navigator-item="let i, let isSelected = selected"…

эквивалентно

<template [list-navigator-item]="let i, let isSelected = selected"
<div …

Angular 2 создает TemplateRef для любого <template>, который он найдет в макете компонента.

Вернемся к createEmbeddedView. Вторым аргументом этот метод получает некий context. Этот объект достаточно важен, поскольку именно с помощью него мы можем инициализировать значения переменных, определенных в шаблоне:

"let i, let isSelected = selected"

Здесь опять имеет место небольшой синтаксический сахар, эту запись Angular 2 понимает как:

"let i=$implicit, let isSelected = selected"

То есть в нашем случае объект context должен иметь два свойства: $implicit и selected. Так оно и есть:

export class ListNavigatorItemContext
{
    constructor(
        public $implicit: any,
        public selected: boolean
    ) { }
}

Теперь у нас есть все знания чтобы понять, как работает list-navigator-item-outlet – как только выставляются оба свойства template и context, директива создает в своем контейнере новый визуальный элемент.

Здесь необходимо сделать следующую оговорку: по-хорошему, надо было бы при повторном вызове ngOnChanges удалять предыдущий созданный визуальный компонент, но в нашем учебном примере в этом нет необходимости.

list-navigator-item


Тут совсем все просто:

@Directive({
    selector: "[list-navigator-item]"
})
export class ListNavigatorItem {
    constructor(@Optional() public readonly templateRef: TemplateRef<ListNavigatorItemContext>) {
    }
}

Единственная цель этой директивы — это передать скомпилированный шаблон через публичное свойство.

list-navigator


Наконец, наш главный компонент, ради которого все и затевалось. Начнем с его макета:

<div *ngFor="let i of itemsToDisplay">
    <div>
        <input 
type="checkbox" 
[ngModel]="i.selected" 
(ngModelChange)="onSelectedChange(i, $event)"/>
    </div>
    <div class="item-wrapper">
        <list-navigator-item-outlet 
[template]="templateOutlet.templateRef" 
[context]="i">
        </list-navigator-item-outlet>
    </div>
</div>
<div>
    <button (click)="onPrev()">Prev</button>
    <button (click)="onNext()">Next</button>
</div>

где:

this.itemsToDisplay = this
    .dataSource(offset, pageSize)
    .map(i => new ListNavigatorItemContext(
       i, this.selectedItems.indexOf(i) >= 0));
…
@ContentChild(ListNavigatorItem)
protected templateOutlet: ListNavigatorItem;

Принцип работы:

  • Получаем очередную порцию объектов, используя ссылку на соответствующую функцию (dataSource) и помещаем ее в itemsToDisplay
  • Перебираем все объекты из порции (*ngFor=«let i of itemsToDisplay») и для каждого объекта создаем галочку плюс вышеописанный list-navigator-item-outlet, который получает в качестве параметров:
    • Контекст состоящий из объекта и признака отметки selected;
    • Ссылку на TemplateRef, которую нам любезно предоставила директива list-navigator-item, которая в свою очередь была найдена путем декларации запроса @ContentChild(ListNavigatorItem)
  • list-navigator-item-outlet создает визуальный элемент для очередного объекта, используя переданный шаблон и контекст (помните, что в реальном проекте желательно использовать NgTemplateOutlet).

Небольшое дополнение.
Шаблон для list-navigator вполне интерактивен — мы можем добавлять кнопки, чекбоксы, поля ввода и управлять всем этим из родительской страницы. Вот пример с добавленной кнопочкой “Archive”, обработчик которой, находится на родительской странице и изменяет состояние пользователя:


<list-navigator..>
  <div  *list-navigator-item="let i..." >
    ...
    <button *ngIf="!i.archived" class="btn-arch" (click)="onArchive(i)">Archive</button>
    ...
  </div>
</list-navigator>

    protected onArchive(user: User){
        user.archived = true;
    }



Вот, собственно, и все. Статья описывает приемы, которые чуть-чуть выходят за рамки стандартной документации для Angular 2, поэтому, надеюсь, она кому-нибудь пригодится. Еще раз указываю ссылку на исходники примеров описанных в статье. (вариант с кнопкой «Archive»).
Поделиться с друзьями
-->

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


  1. irsick
    25.01.2017 19:31

    Вот блин, а я мучился с именованными router-outlets

    router

    children: [
        { path: '', component: headerComponent, outlet: 'header' },
        { path: '', component: contentComponent },
        { path: '', component: headerComponent, outlet: 'footer' },
    ]
    


    layout
    <section>
        <router-outlet name="header"></router-outlet>
        <router-outlet></router-outlet>
        <router-outlet name="footer"></router-outlet>
    </section>
    


    1. 0x1000000
      26.01.2017 00:17

      Для подобных целей в Angular 4 будет добавлен NgComponentOutlet,
      а пока можете создать полифил ;)


  1. lega
    25.01.2017 21:35

    Хороший пример того, что Angular2 переусложнен*, вот аналог на alight.

    отражение содержимого декларации компонента в его DOM c помощью специального тега… list-navigator-item-outlet, list-navigator-item-сontext, list-navigator-item...
    Я просто взял содержимое элемента (itemTpl = element.innerHTML) и вывел в его списке.


    1. 0x1000000
      25.01.2017 23:40
      +2

      Ангуляр 2 это действительно непростой инструмент, но для сложных задач должен быть выбран советующий инструмент. Тут можно привести аналогию – лопата и экскаватор. Лопату освоить просто, экскаватор сложнее. Но сколько можно выкопать лопатой, а сколько экскаватором за один и тот же отрезок времени? Другое дело, что если нужна одна небольшая ямка в труднодоступном месте и при ограниченном бюджете, то экскаватор вообще не вариант. Я использую второй ангуляр в достаточно крупном проекте (те самые тысячи страниц =) ) уже в течении года + до этого пол года на “Proof of concept” (начинали еще с альфы), и могу сказать, что этот фреймворк просто великолепен как комплексное решение, но я вполне понимаю недоумение людей впервые смотрящих на “Hello World” – тут действительно ангуляр 2 кажется переусложненным.


      По поводу вашего примера на alight хочу выразить вам благодарность – действительно интересно как одни и те же задачи решаются с использованием разных инструментов. К сожалению, я не знаком с alight поэтому не могу оценить простоту, но по поводу использования innerHTML у меня есть подозрение, что будет сложно добавить интерактив к шаблону будет непросто (могу ошибаться).


      Вот вариант на Ангуляр 2 с кнопкой “Archive” клик на которую обрабатывается родительской страницей. Потребовался лишь минимум изменений.


      1. lega
        26.01.2017 00:26
        +2

        Не соглашусь с аналогией. Вообще путь Ангуляра 2 непонятен, вот 1-я версия родилась из реального использования и конкретных потребностей. А в ангуляр 2 понапихали все что только можно, все хиптерские кейворды, да они пофиксили косяки первой версии, но и привнесли других, новых проблем, при этом они сами его не используют, это больше похоже на исследовательский (академический) проект.

        не могу оценить простоту
        Можно сравнить хотя бы объем, 8 строк нативного js — само приложение и 20 строк на компонент.
        Потребовался лишь минимум изменений.
        Аналогично


        1. sentyaev
          26.01.2017 03:25

          Вообще путь Ангуляра 2 непонятен

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

          в ангуляр 2 понапихали все что только можно

          Тоже спорно. По моему мнению понапихали то что нужно. Плюс в том что вы можете не использовать некоторые части если они вам не нужны (например можно не использовать роутер или формы). Второй момент — почти каждый элемент можно тюнить.
          Единственное чего не хватает это интегрированного moment.js (но это всетаки проблема не angular, а JS).

          Я все это к тому, что angular это не мелкая штука, действительно нужно потратить какое-то время чтобы в нем разбраться (плюс еще нужно освоить Typescript + RxJS).


      1. raveclassic
        26.01.2017 01:22

        Я использую второй ангуляр в достаточно крупном проекте (те самые тысячи страниц =) ) уже в течении года + до этого пол года на “Proof of concept” (начинали еще с альфы), и могу сказать, что этот фреймворк просто великолепен как комплексное решение
        А можете привести пример места, где он значительно помог как движок? И какая его часть/части. На всякий случай уточню — помог именно как комплексное решение, которое недостижимо при использовании отдельных аналогов его компонент.


        1. sentyaev
          26.01.2017 03:52

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

          Не совсем корретный вопрос, если вы говорите, что angular — это комплексное решение, то решение, собранное из нескольких компонент тоже является комплекным. Просто поставщик этого ренения не Google, а вы.

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

          У нас в команде путь с angular был примерно такой (все .net разработчики):
          1. Прикольно.
          2. Чет сложно.
          3. Зря выбрали angular.
          4.…
          5. Ох он мощьный.
          6. Когдаже они добавят поддежку TS 2.1, хочется acync/await.


          1. radtie
            26.01.2017 10:27
            +1

            Бык это ж классика:

            Заголовок спойлера
            image


          1. raveclassic
            26.01.2017 11:53
            +1

            если вы говорите, что angular — это комплексное решение
            Ну да, кривовато получилось, надо было в кавычки взять. На самом деле это просто цитата:
            фреймворк просто великолепен как комплексное решение

            Ну и вопрос был не про кривую изучения, излишнюю переусложненность и прочее. Вопрос был про реальные кейсы (кстати, вы тоже можете рассказать), когда ng2 (независимо от того, кто за ним стоит и кто поддерживает) сильно помог. Пока-что все доводы, что я слышал, это «ну, это большой движок, все в одном флаконе, поддерживает гугл, лень собирать из библиотек». Чем же он так хорош? Расскажите, мне действительно интересно.

            UPD: Мне, как человеку, использующему большую часть времени реакт (не кидайтесь камнями, я мирно тут :) ), достаточно дико видеть такое месиво ради расширения шаблона переиспользуемого компонента. Но, все-равно, интересно, что я выиграю с ng2.


            1. sentyaev
              26.01.2017 12:58

              Есил вы эксперт React'a, а еще лучше если у вас вся команда свободно работает с React и у вас есть набор бибилотек которые вы используете совместно с React (те у вас есть свое «комплексное решение»), то ничем вам angular не поможет. Условно конечно, но это как спрашивать: «Я эксперт по JavaEE, чем мне поможет ASP.NET/RoR».

              Попробую перечислить некоторые моменты которые для меня были решающими при выборе angular vs React:
              1. Typescript
              2. Разделение разметки(templates) и кода компонента
              3. DI
              Но кого волнует что мне нравиться, а что нет?

              ну, это большой движок, все в одном флаконе

              Вот это на самом деле и есть ключевой момент. А с ростом популярности angular он станет еще важнее.
              Т.е. стандартизация. Если посмотреть на C#, Java, Ruby, то у каждого языка есть 1) стандартная библиотека, 2) один-два фреймворка для разработки веб-приложений. Опустим остальное, мы же про веб говорим. Другими словами проще нанять человека в команду.


              1. raveclassic
                26.01.2017 14:10

                Ну понятно, что-то подобное мне и представлялось.
                Да, у нас весь стэк вокруг реакта, но есть проект на первом ангуляре, вот думаем, что с ним делать, переводить на привычный стэк (т.е. с нуля), либо ng2. Но, что-то мне подсказывает, что второе — это то же самое с нуля. Только попутно собирая все неизвестные подводные камни.

                Пару слов по поводу решающих моментов:
                1. У нас тоже TS/TSX
                2. Ну, думается мне, это больше философия, и ангуляр, как и многие другие движки, разделяющие шаблоны и логику, тут не причем.
                3. DI и отдельных реализаций много

                По поводу «стандартизации» все так. Но это плюс всех монолитов. Другое дело, что в экосистеме реакта присутствуют достаточно устоявшийся выбор, роутинг, компоненты, темизация, формы… Так что, я бы не стал углубляться в поиск преимуществ монолита на хорошей поддержке. А людей, как показывает практика, можно найти в обоих лагерях.

                Однако, вопрос, все же, все-равно не о том. Я постоянно слышу, что ng2 — это сложное решение для сложных проблем. И хотелось бы услышать про эти сложные случаи, когда именно внутренняя инфраструктура ng2, какие-то его компоненты, архитектура, помогли решить эти сложные проблемы. Чтобы, раз — попытаться построить аналогию на своем стэке (а пока-что за неимением представления об этих случаях она строится на ура), два — попытаться, взвесив все за и против, иметь представление, что к чему.


                1. sentyaev
                  26.01.2017 15:08

                  Да, у нас весь стэк вокруг реакта, но есть проект на первом ангуляре, вот думаем, что с ним делать, переводить на привычный стэк (т.е. с нуля), либо ng2.

                  Хм, так ведь неважно на что вы будете переводить, вам все равно придется все с нуля писать. Я слабо верю что там можно A1 в A2 мигрировать без тотального переписывания и кучи грабель.

                  Другое дело, что в экосистеме реакта присутствуют достаточно устоявшийся выбор, роутинг, компоненты, темизация, формы…

                  Ну тогда по сути и разницы нет.

                  Я постоянно слышу, что ng2 — это сложное решение для сложных проблем.

                  Я не думаю, что React проще Angular. Вы же все равно осваивали «экосистему React». Да и решают они одни и те же задачи.
                  Да и вообще, я вижу React/Angular как JavaEE/ASP.NET или BMV/Mersedes.


                  1. raveclassic
                    26.01.2017 15:30

                    Хм, так ведь неважно на что вы будете переводить, вам все равно придется все с нуля писать. Я слабо верю что там можно A1 в A2 мигрировать без тотального переписывания и кучи грабель.
                    Ну, иделология и архитектура (по большей части) сохраняются.

                    Я не думаю, что React проще Angular. Вы же все равно осваивали «экосистему React». Да и решают они одни и те же задачи.
                    Я к этому и клоню. Просто, видя во что превращается кастомизация внутреннего шаблона компонента, невольно задаешься вопросом: «зачем-то же это все тут накручено?».


                    1. sentyaev
                      26.01.2017 17:01

                      зачем-то же это все тут накручено?

                      Собственно эта статья и объясняет это, хотябы частично. Вы же со своей экспертизой можете прикинуть как эту задачу решить используя React.


                      1. raveclassic
                        26.01.2017 18:19
                        +1

                        Собственно эта статья и объясняет это, хотябы частично
                        Не совсем с вами согласен. Статья показывает, как накрутить. А зачем все это было придумано разработчиками движка не объясняет (да и не должна).

                        как эту задачу решить используя React.
                        Мы уже ее решили. позволю себе не повторять изначальный пример точь в точь, а только отразить суть.
                        Кастомизацию мы делаем примерно вот так несколькими способами:
                        class Widget extends React.Component {
                            state = {}
                        
                            render() {
                                const {settingsMode} = this.state;
                                const {settings, children} = this.props;
                        
                                return (
                                    <div className="widget">
                                        <div className="widget--header">
                                            <button onClick={this.onButtonClick}>
                                                {settingsMode ? 'OK' : 'Settings'}
                                            </button>
                                        </div>
                                        <div className="widget--body">
                                            {settingsMode && (
                                                <div>
                                                    Settings: {settings}
                                                </div>
                                            )}
                                            {!settingsMode && children}
                                        </div>
                                    </div>
                                );
                            }
                        
                            onButtonClick = () => {
                                this.setState({
                                    settingsMode: !this.state.settingsMode
                                });
                            }
                        }
                        
                        class ListNavigator extends React.Component {
                            state = {}
                        
                            render() {
                                const {items, Item} = this.props;
                        
                                return (
                                    <div className="navigator">
                                        {items.map((item, i) => <Item key={i} {...item}/>)}
                                    </div>
                                );
                            }
                        }
                        
                        //can be a class with its own state and access to context
                        const SeparateItem = ({id, name}) => (
                            <div>
                                Separate: {id}: {name}
                            </div>
                        );
                        
                        const items = [
                            {
                                id: 123,
                                name: 'Foo',
                            },
                            {
                                id: 234,
                                name: 'Bar'
                            }
                        ];
                        
                        class App extends React.Component {
                            render() {
                                const settings = (
                                    <div>settings</div>
                                );
                        
                                return (
                                    <div>
                                        <Widget settings={settings}>
                                            <div>
                                                Large widget body
                                            </div>
                                        </Widget>
                                        As separate component
                                        <ListNavigator items={items} Item={SeparateItem}/>
                                        As a method
                                        <ListNavigator items={items} Item={this.renderItem}/>
                                    </div>
                                );
                            }
                        
                            renderItem = item => {
                                return (
                                    <div>
                                        <span>{item.id}</span>
                                        <span>{item.name}</span>
                                    </div>
                                );
                            }
                        }
                        


                        1. 0x1000000
                          26.01.2017 19:05

                          В ангуляре мы тоже, по сути, передаём «функцию» в компонент:


                          <template> компилируется в TemplateRef у которого есть метод createEmbeddedView(context: C)


                          Хотя соглашусь, в React это выглядит изящнее



            1. 0x1000000
              26.01.2017 14:00

              Все ждал когда про Реакт вспомнят =)


              Реальные кейсы где Angular 2 выигрывает это скорее экономические и политические, нежели чисто технические.


              Например, поиск новых разработчиков: если человек знает Angular 2, то можно сразу в бой, поскольку все проекты на Ангуляре 2 похожи, но если он знает React, то далеко не факт, что он сможет сходу начать работу (на проекте с React разумеется).


              Кроме того, надежда на то, что создатели Ангуляра не бросят разработку (поддержку) в среднем выше чем для многих других решений. Первый ангуляр до сих пор обновляют.



              1. raveclassic
                26.01.2017 14:18

                но если он знает React, то далеко не факт, что он сможет сходу начать работу (на проекте с React разумеется).
                У вас есть пример? Пока-что никаких проблем не было. А вот с первым ангуляром были. Но только из-за того, что в этом проекте лютейшее легаси из говна и палок в силу излишней гибкости движка.
                Кроме того, надежда на то, что создатели Ангуляра не бросят разработку (поддержку) в среднем выше чем для многих других решений. Первый ангуляр до сих пор обновляют.
                У гугла очень специфичное отношение к поддержке собственных продуктов и решений. Уверенности, что ng им в один момент не надоест вообще никакой. Другое дело, что его 100% не бросит сообщество. Та же история и с фейсбуком, вообще не понятно, что там будет дальше. Но всегда есть сообщество.

                UPD
                Все ждал когда про Реакт вспомнят =)
                И хотелось бы, чтобы это не выходило за рамки мирного обсуждения :)



  1. raveclassic
    25.01.2017 22:53

    Смотрю я на все это безобразие, на жуткий dsl, на ворох всяких символов, маркеров, скобок, на груду разных сущностей, компонентов, директив, какие-то еще безумных декораторов, на кучу разных классов, селекторов, каких-то рефов, контекстов… Слезы наворачиваются, ей богу.


    1. pilot911
      26.01.2017 08:30

      я таким взглядом смотрю на современный C++


  1. ai3
    26.01.2017 12:15
    +1

    0x1000000 исходники затёрлись… Восстановите?


    1. 0x1000000
      26.01.2017 12:17

      Это какая-то бага Plunker — перестал работать в «embed» режиме. Поменял все ссылки на «edit» режим. Спасибо, что сообщили.


  1. max1gu
    27.01.2017 22:19

    Глядя на это вспомнилась технология xml/xsl, котрая работала 15 лет назад в ИЕ.
    Там все тоже самое — можно сделать шаблоны для собственного тега с обработкой вложенных в свой тег дочерних элементов.
    Годная вещь была, немного сложноватая на старте.
    Однако, проблема таже самая, что и до сих пор — js код никак не привязан к элементам браузера и приходилось придумывать лисапеды по навешиванию событий на сгенерированный html, чтобы в итоге работать с тегом как с объектом.

    Когда нибудь web components сделают переворот в разработке. Когда-нибудь. А пока 15 лет потрачены на непонятно кому нужные новые стандарты и языковую вкусовщину, которая почему перекомпилируется в древний js.


    1. 0x1000000
      27.01.2017 23:26

      Сам 10 лет назад делал веб приложения на XSLT – вполне работоспособный вариант (сайт до сих пор работает :) ). Там правда была задача уменьшить размер загружаемых на клиента данных, поскольку интернет в те времена не был так быстр.