2016-12-03 6 views
1

Я пытаюсь сортировать массив объектов модели на основе одной из связанных с ними моделей.Ember ждет отношений для загрузки в массиве с помощью ember concurrency

У меня есть документ который hasManyLINEITEM и LINEITEMbelongsTo в Расположение. Я хочу сортировать позиции по их местоположению. Так что, если я что-то вроде этого:

let lineItems = [ 
    { 
    itemName: "One", 
    location: { 
     name: "Kitchen" 
    } 
    }, 
    { 
    itemName: "Two", 
    location: { 
     name: "Kitchen" 
    } 
    }, 
    { 
    itemName: "Three", 
    location: { 
     name: "Bathroom" 
    } 
    }, 
    { 
    itemName: "Four", 
    location: { 
     name: "Bathroom" 
    } 
    }, 
    { 
    itemName: "Five", 
    location: { 
     name: "Hall" 
    } 
    }, 
    { 
    itemName: "Six", 
    location: { 
     name: "Hall" 
    } 
    } 
]; 

Я хочу, чтобы добраться до:

let groupedLineItems = { 
    "Kitchen": [ 
    { 
     itemName: "One", 
     location: "Kitchen" 
    }, 
    { 
     itemName: "Two", 
     location: "Kitchen" 
    }, 
    ], 
    "Bathroom": [ 
    { 
     itemName: "Three", 
     location: "Bathroom" 
    }, 
    { 
     itemName: "Four", 
     location: "Bathroom" 
    }, 
    ], 
    "Hall": [ 
    { 
     itemName: "Five", 
     location: "Hall" 
    }, 
    { 
     itemName: "Six", 
     location: "Hall" 
    } 
    ] 
} 

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

Это мой контроллер:

~/app/controllers/document.js 

import Ember from 'ember'; 
import _ from 'underscore'; 
import { task } from 'ember-concurrency'; 
const { computed } = Ember; 

export default Ember.Controller.extend({ 
    init() { 
     this.get('myTask').perform(); 
    }, 

    myTask: task(function *() { 
     // Wait for locations to resolve. 
     let lineItems = this.get('model').get('lineItems'); 

     yield this.get("secondTask").perform(lineItems); 

     let groupedLineItems = yield _.groupBy(lineItems.toArray(), (lineItem) => { 
      lineItem.get('location').then((location) => { 
       return location.get('name'); 
      }); 
     }); 

     this.set("groupedLineItems2", Ember.Object.create(groupedLineItems)); 
    }), 

    secondTask: task(function * (lineItems) { 
     yield lineItems.toArray().forEach((lineItem) => { 
      lineItem.get('location'); 
     }); 
    }) 
}); 

Это WIP на контроллере и не правильно. При запуске запускается первая задача, но не дожидаются загрузки местоположений для каждой позиции. Я пытаюсь заставить его загрузить все места во второй задаче, но явно не подходит для этого. Следовательно, при инициализации он будет выполняться путем сортировки, но функция для _.groupBy вернется не определена, потому что в этой точке местоположение не определено. Затем, если я снова запустил задачу вручную (используя кнопку в шаблоне) после того, как я узнаю, что места загружены (потому что я использую их в другом месте в шаблоне), он работает нормально.

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

+0

Это не «сортировка». –

ответ

1

Отличный вопрос!

Вот как я это сделаю.

models/document.js:

import Ember from 'ember'; 
import Model from "ember-data/model"; 
import attr from "ember-data/attr"; 
import { belongsTo, hasMany } from "ember-data/relationships"; 

export default Model.extend({ 
    lineItems: hasMany('line-item'), 

    groupedLineItemsPromise: Ember.computed(function() { 
    return this 
     .get('lineItems') 
     .then(lineItems => { 
     const locations = Ember.RSVP.all(lineItems.map(item => item.get('location'))) 
     return Ember.RSVP.hash({lineItems, locations}) 
     }) 
     .then(({lineItems}) => { 
     return lineItems.reduce((result, item) => { 
      const locationName = item.get('location.name') 
      result[locationName] = result[locationName] || [] 
      result[locationName].pushObject(item) 
      return result 
     }, {}) 
     }) 
    }), 

    groupedLineItemsProxy: Ember.computed('groupedLineItemsPromise', function() { 
    return Ember 
     .Object 
     .extend(Ember.PromiseProxyMixin) 
     .create({ 
     promise: this.get('groupedLineItemsPromise') 
     }) 
    }) 
}); 

Демо: https://ember-twiddle.com/3f7ee2371c424c456c48347774395191?openFiles=models.document.js%2C

Обратите внимание, что вы на самом деле не нужно ember-concurrency для этого. Для достижения результата я использую старые старые рассчитанные свойства.

Но дело в том, что это неправильный подход, в первую очередь, ember-concurrency или нет:

  1. Этот подход будет генерировать тонны сетевых запросов, если не использовать плохо документированная особенность группирования сетевых запросов (и ваш бэкэнд поддерживает его).
  2. В случае сбоя любого из запросов ваше приложение будет вводить несогласованное состояние. Вероятно, вся задача параллелизма/вычисляемое свойство будет терпеть неудачу, и у пользователя не будет возможности перезапустить неудавшийся запрос.
  3. Ваша страница будет отображаться преждевременно, без содержания. Затем содержимое в конечном итоге появится.

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

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

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

+0

Спасибо, такой хороший ответ! В конце концов я понял, что мне нужно сделать: 'вернуть this.get ('магазин') findRecord ('документ', params.document_id, {включают в себя: 'lineItems.location'});' В. мой крючок модели на маршруте. Тогда я мог бы просто сделать: 'groupedLineItems: computed ('model.lineItems. @ Each.location', function() { const lineItems = this.get ('model'). Get ('lineItems'). ToArray (); return this.sortObject (_. GroupBy (lineItems, (lineItem) => lineItem.get ('location'). Get ('name'))); }) ' – brownie3003