Как я должен делиться данными между компонентами Ember?

У моего приложения Ember есть маршрут, который содержит 2 разных компонента и один контроллер с шаблоном index.hbs.

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

1) Пользователь может выбрать несколько фильтров из раскрывающихся списков компонента «Фильтр».

2) DataGrid - это отдельный компонент от фильтра.

3) Пользователь может выбрать несколько строк из DataGrid, установив флажки

4) Кнопка «Создать пользовательский отчет» запускает «sendAction» для контроллера маршрута

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

Лучшими практиками Ember являются «Data Down / Actions Up», и из того, что я прочитал, вы не должны пытаться получить доступ к компоненту из контроллера.

Проблема, однако, в том, чтоМетод createCustomReport в контроллере должен иметь доступ ко всем фильтрам, которые были выбраны в компоненте фильтра, а также ко всем строкам, которые были проверены в компоненте grid.

Мой первый инстинкт - установить свойства самого компонента - он должен поддерживать свое собственное состояние - затем получить ссылку на компонент из контроллера, чтобы получить его состояние перед передачей его функции отчета.

Но, видимо, это нет-нет.

Вот мое текущее решение:

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

Кроме того, каждый раз, когда я выбираю флажок в сетке, другой компонент sendAction переходит к компоненту, затем всплывает к контроллеру и устанавливает пользовательское свойство на контроллере для выбранных строк сетки.

Затем, когда я нажимаю «createCustomReport», метод, который запускается в контроллере, получает доступ к свойствам, которые я установил ранее - потому что все они теперь находятся на контроллере.

Так это выглядит примерно так:

import Ember from 'ember';

export default Ember.Controller.extend({

    myFirstFilter: undefined,
    mySecondFilter: undefined,

    actions: {
        createCustomReport() {
            // do something with all those component properties you've been setting
        },

        // These are triggered by the sendAction on the respective component
        firstFilterMethod(myProperty1) {                
            this.set('myFirstFilter', myProperty1.name);
        },

        secondFilterMethod(myProperty2) {               
            this.set('mySecondFilter', myProperty2.name);
        },

        ... etc...


    }
});

Вот моя проблема с этим

Я не напрямую обращаюсь к компонентам из контроллера, но, используя принцип «Действия вверх», я устанавливаю свойства контроллера, которые зависят от вида.

Исходя из фона Sencha ExtJS, где контроллеры имеют ссылки на свои представления, я нахожу это очень странным.

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

Это считается «лучшей практикой» в Ember или есть лучший способ для меня получить данные всех этих отдельных компонентов, чтобы запустить метод createCustomReport?

 PhillipKregg07 июл. 2016 г., 00:33
@MirzaMemic Причина, по которой я использую компоненты, заключается в том, что приложение будет усложняться, и я хочу иметь возможность повторно использовать функциональные возможности без его тесной связи. Например, другие части приложения будут использовать компонент фильтра, но не будет сетки. И в приложении будут места, которые используют сетку данных без фильтров.
 Mirza Memic07 июл. 2016 г., 00:19
Интересный пост. Когда вы разработали это, что было главной причиной использования компонентов и почему у вас было два из них?
 acorncom08 июл. 2016 г., 06:18
Судя по тому, как это выглядит, я бы не ожидал, что маршрутизируемые компоненты появятся после того, как появится Glimmer 2 (вероятно, 2.9ish). Кто-то сегодня гадал пост 2.12. Достаточно далеко, что люди из основной команды и команда обучения (в которую я вхожу) поощряют людей просто использовать контроллеры. У вас больше шансов получить инструменты, которые помогут автоматизировать переход (например, инструмент ember-watson или его эквивалент), если вы пока что будете следовать официальным рекомендациям. По крайней мере, это то, что я делаю в большом клиентском приложении, над которым я работаю ;-)
 PhillipKregg08 июл. 2016 г., 02:29
@acorncom Вы бы порекомендовали мне заменить мои контроллеры чем-то вроде добавления ember-route-action-helper, пока не выйдут маршрутизируемые компоненты? Вот репо:github.com/DockYard/ember-route-action-helper
 acorncom07 июл. 2016 г., 13:49
Официально контроллерыне устарела, но есть планы заменить их в какой-то момент :-)
 Mirza Memic07 июл. 2016 г., 06:09
@PhillipKregg Это угасающий путь. Единственное, что createCustomReport не должен находиться внутри контроллера, так как контроллер должен быть декоратором представления. Официально контроллеры устарели в пользу маршрутизируемых компонентов, но они еще не приземлились. Поскольку вы будете использовать их позже, имеет смысл создать их как компонент, и здесь у вас есть «угасающий» способ. Еще одна вещь, которую стоит рассмотреть, должны ли некоторые фильтры быть паролями запроса - чтобы у вас был выбор фильтра по URL-адресу #route? Filter1 = value => после посещения маршрута, в котором фильтр предварительно выбран.

Ответы на вопрос(1)

Хорошо, я думаю, что мне удалось решить мою собственную проблему и найти способ, которым Ember делает вещи.

Я нашел 2 разных решения, каждое из которых имеет свои достоинства. Кроме того, я создал 2 небольших мини-урока Ember Twiddle о том, как решать вопросы распространения состояния и совместного использования данных компонентов.

Оба решения полностью соответствуют стандарту Ember 2.6:Контроллеры не нужны.

Первый использует Ember Service.

Я создал простой список фильмов, который можно посмотреть здесь:https://ember-twiddle.com/c91e98cd255a556311417ac603ab0315

Следуя комментариям внутри файлов и просматривая Ember Twiddle выше, следует ответить на все ваши вопросы о том, как это реализовать.

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

Вот как выглядит компонент:

import Ember from 'ember';

export default Ember.Component.extend({
  movieService: Ember.inject.service('movie-displayer-service'),
  currentSelectedMovie: '',

  didInsertElement: function() {
    // When the component is inserted into the DOM tree, use the model to set
    // the 'currentSelectedMovie' property.
    this.set('currentSelectedMovie', this.get('model').currentSelectedMovie);   
  },

  actions: {
    selectMovie: function(movie) {
      // Instead of saving state in the component itself, let's
      // save it in a service that can be consumed anywhere
      // in the application.

     this.get('movieService').setupCurrentSelectedMovie(movie);

     // When the movie changes, we can override the 'currentSelectedMovie' property
     // that is being populated with the 
     this.set('currentSelectedMovie', movie);   

    }
  }
});

Вот как выглядит Сервис:

import Ember from 'ember';

export default Ember.Service.extend({
  currentSelectedMovie: undefined,

  setupCurrentSelectedMovie: function(movie) {
   this.set('currentSelectedMovie', movie); 
  },

  showSelectedMovie: function() {
    if (this.get('currentSelectedMovie')) {
        alert("The current selected movie of the movie-displayer component is:  \n" + this.get('currentSelectedMovie'));
    } else {
        alert('Please Select a Movie First');
    }
  }
});

Вот файл руля компонента:

<div class="movie-list-container">
    <h4 class="movie-list-title">Movie List</h4>

  <ul>
    {{#each model.movies as |movie|}}

        {{!--   'eq' is a helper function that I made
                    to compare two values.  You can check it out in
              the 'helpers' folder.
      --}}
        <li class="{{if (eq movie currentSelectedMovie) "selected" "not-selected"}}" {{action 'selectMovie' movie}}>{{movie}}</li>
    {{/each}}
  </ul>

</div>

И вот как выглядит маршрут:

import Ember from 'ember';

export default Ember.Route.extend({
  movieService: Ember.inject.service('movie-displayer-service'),

  model: function() {
    return {
        currentSelectedMovie: this.get('movieService').currentSelectedMovie,

      movies: ['Captain America: Civil War', 'Guardians of the Galaxy', 'Ant Man']
    }
  },

  actions: {
    showServiceState: function() {
        this.get('movieService').showSelectedMovie();
    }
  }
});

Плюсы сервисного решения:

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

Минусы сервисного решения:

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

Второе решение отправляет состояние от компонента к маршрутизатору без службы

Второй Ember Twiddle - это простой список ресторанов, который показывает, как распространять информацию о состоянии без необходимости обслуживания:

https://ember-twiddle.com/dffc679fb96434ba6698161ba7617d15

Вот файл handlebars компонента:

<div class="restaurant-list-container">
  <ul>
    {{#each model as |restaurant|}}
      <li class="{{if (eq currentlySelectedRestaurant restaurant ) 'selected' 'not-selected' }}" {{action 'selectRestaurant' restaurant}}>{{restaurant}}</li>
    {{/each}}
  </ul>

</div>

Вот файл маршрута:

import Ember from 'ember';

export default Ember.Route.extend({  
  // Properties Here
    currentlySelectedRestaurant: 'Please Select a Restaurant',

  model: function() {
    return ['Taco Bell', 'McDonalds', 'Dennys']
  },

  actions: {
    setupRestaurantState : function(restaurant) {
        this.set('currentlySelectedRestaurant', restaurant);
    },

    getComponentState: function() {
     alert(this.get('currentlySelectedRestaurant'));
    }
  }
});

И вот файл компонента:

import Ember from 'ember';

export default Ember.Component.extend({

  currentlySelectedRestaurant: undefined,

  actions: {
    selectRestaurant: function(restaurant) {

      // The 'sendAction' method is where the magic happens.
      // A method called 'stateSetter' references a function
      // that lives either on the controller or the route.
      // This was setup when the component was instantiated in the
      // fancy-restaurants.hbs file.
      this.sendAction('stateSetter', restaurant);
      this.set('currentlySelectedRestaurant', restaurant);
    }
  }
});

Обратите внимание, что маршрут содержит неопределенное свойство состояния: 'nowSelectedRestaurant'.

Это может быть объект с несколькими свойствами или массив.

Вы также можете иметь общее имя, такое как "componentState", и хранить все, что вы решите отправить из любого компонента: например, параметры проверяются в отфильтрованном списке или выбираются элементы из сетки.

Плюсы неиспользования Сервиса:

Это проще сделать. Просто используйте sendAction () в вашем компоненте, чтобы всплыть на маршрутизатор. И нет никаких дополнительных файлов, созданных или каких-либо зависимостей.

Минусы не использования Сервиса

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

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

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

Ваш ответ на вопрос