I realize there have been several questions similar to this, but none of those answers seems to be resolving my issue. My objective is to take a list of language's, and filter them so that my template can display a subset of the full list.
I started off by verifying that my computed property is working:
MyController.js
// Works as expected
languagesFiltered: function() {
return this.get('languages');
}.property('languages')
Then I added in a filter function, but here's where I ran into trouble:
MyController.js
languagesFiltered: function() {
// console.log shows that languages is actually a promise
var languages = this.get('languages');
// all of this returns a promise, but Handlebars can't handle the promise
return languages.then( function( languagesArray ) {
return languagesArray.filter( function( item, index, enumerable) {
return item.get('name') !== 'English';
});
})
}.property('languages')
I'm attempting to use the Ember.Array.filter method (http://emberjs.com/api/classes/Ember.ArrayProxy.html#method_filter). The filter seems to be working correctly, but now languagesFiltered returns a promise, and Handlebars can't handle that.
I tried one last alternative:
MyController.js
languagesFiltered: function() {
var languages = this.get('languages');
// "return languages;" works
// But "return languages.filter" returns an empty array
return languages.filter( function( item, index, enumerable ) {
console.log(item);
return true;
});
}.property('languages')
And console.log(item) never gets called. So my questions are:
What is the best way to implement the simple filter I'm after?
This is a read-only computed property, but what are best practices for handling async values in computed properties?
I'm using Ember 1.7.0-beta4, Ember Data 1.0.0-beta10, and ember-cli 0.44. I'd upgrade to Ember 1.7.0, but there's a small bug that affects another part of our app, so we're waiting until 1.7.1. Thanks for your input!
You can try returning a PromiseArray instead of just the promise.
You should be able to do something like..
languagesFiltered: function() {
// all of this returns a promise, but Handlebars can't handle the promise
var promise = this.get('languages').then( function( languagesArray ) {
return languagesArray.filter( function( item, index, enumerable) {
return item.get('name') !== 'English';
});
})
return DS.PromiseArray.create({
promise: promise
});
}.property('languages')
Now there are few better solutions. I use ember-promise-helpers.
So can keep your languagesFiltered code intact and do the following inside your hbs:
{{#each (await languagesFiltered) as|lang|}}
...
...
More – https://emberigniter.com/guide-promises-computed-properties/
Related
I currently use a {{link-to}} helper that was written by someone else to explicitly state the query params to pass to the next route and strip out others that are not stated. It looks like this:
//link-to example
{{#link-to 'route' (explicit-query-params fromDate=thisDate toDate=thisDate)} Link Text {{/link-to}}
//the helper
import {helper} from '#ember/component/helper';
import Object from '#ember/object';
import {assign} from '#ember/polyfills';
export function explicitQueryParams(params, hash) {
let values = assign({}, hash);
values._explicitQueryParams = true;
return Object.create({
isQueryParams: true,
values,
});
}
export default helper(explicitQueryParams);
// supporting method in router.js
const Router = EmberRouter.extend({
_hydrateUnsuppliedQueryParams(state, queryParams) {
if (queryParams._explicitQueryParams) {
delete queryParams._explicitQueryParams;
return queryParams;
}
return this._super(state, queryParams);
},
});
I've recently had a use case where I need to apply the same logic to a transitionTo() that is being used to redirect users from a route based on their access:
beforeModel() {
if (auth) {
this.transitionTo('route')
} else {
this.transitionTo('access-denied-route')
}
},
I am really struggling to see how I can refactor what I have in the handlebars helper to a re-usable function in the transitionTo() segment. I'm even unsure if transitionTo() forwards the same arguments as {{link-to}} or if I will have to fetch the queryParams somehow from a different location.
Any insight would be greatly appreciated.
Well first off, tapping into private methods like _hydrateUnsuppliedQueryParams is risky. It will make upgrading more difficult. Most people use resetController to clear stick query params. You could also explicitly clear the default values by passing empty values on the transition.
But, ill bite because this can be fun to figure out :) Check this ember-twiddle that does what you're wanting.
If you work from the beginning in the transitionTo case, we can see that in the router.js implementation:
transitionTo(...args) {
let queryParams;
let arg = args[0];
if (resemblesURL(arg)) {
return this._doURLTransition('transitionTo', arg);
}
let possibleQueryParams = args[args.length - 1];
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
queryParams = args.pop().queryParams;
} else {
queryParams = {};
}
let targetRouteName = args.shift();
return this._doTransition(targetRouteName, args, queryParams);
}
So, if the last argument is an object with a query params obj, that's going directly into _doTransition, which ultimately calls:
_prepareQueryParams(targetRouteName, models, queryParams, _fromRouterService) {
let state = calculatePostTransitionState(this, targetRouteName, models);
this._hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService);
this._serializeQueryParams(state.handlerInfos, queryParams);
if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);
}
}
which has the _hydrateUnsuppliedQueryParams function. So, to make this all work, you can't share the function directly from the helper you've created. Rather, just add _explicitQueryParams: true to your query params. Job done :)
The link-to version is different. The query params use
let queryParams = get(this, 'queryParams.values');
since the link-to component can take a variable number of dynamic segments and there needs to be some way to distinguish between the passed dynamic segments, a passed model, and query params.
The following returns all active items.
activeItems: Ember.computed('items.#each.status', {
get() {
return this.get('items').filter((item) => {
return item.get('status') === 'active';
});
}
})
// Template
{{#each activeItems as |activeItems|}}
{{activeItem.status}}
{{/each}}
All of above works. Now lets say I want to create a computed property that picks out the last activeItem. I tried:
activeItem: Ember.computed('activeItems', {
get() {
return this.get('activeItems').lastObject;
}
}),
// Template
{{activeItem.status}} <-- returns nothing
Why is this and how can I get it to work?
I see 2 problems:
Your computed property has the incorrect dependent key. By only relying on activeItems, the problem will only update when the activeItems property returns a new object, not when the contents are updated. You should be watching activeItems.lastObject.
lastObject is a computed property, which means you might not be able to access it without using Ember's get() function.
Try this:
activeItem: Ember.computed('activeItems.lastObject', {
get() {
return this.get('activeItems.lastObject');
}
})
Or, for the shorthand:
activeItem: Ember.computed.reads('activeItems.lastObject')
Say I have an Ember.Object obj, with a property propPath.
I'm trying to implement:
function isComputedPropertyWithNoSetter(obj, propPath) {
// what do I do here?
// something involving Ember.meta(obj) perhaps?
}
So I can do:
var hasStaticProp = Ember.Object.extend({ prop: 5 }).create();
isComputedPropertyWithNoSetter(hasStaticProp, 'prop');
// => false
var hasComputedPropertyWithSetter = Ember.Object.extend({ prop: function (k, v, d) { }.property() }).create();
isComputedPropertyWithNoSetter(hasComputedPropertyWithSetter, 'prop');
// => false
var hasComputedPropertyNoSetter = Ember.Object.extend({ prop: function () { }.property() }).create();
isComputedPropertyWithNoSetter(hasComputedPropertyNoSetter, 'prop');
// => true
I'm writing 'tree-walking' state serialization code for a large established ember codebase. When I restore state, I want a guard check to make sure I never accidentally overwrite a read-only (getter only) computed property with a static value.
I need to implement this function so I can do....
if (!isComputedPropertyWithNoSetter(obj, propPath) {
// not going to accidentally overwrite a computed property with a static value
Ember.set(obj, propPath, serializedStaticValue);
}
I realize this is fairly dicey, and the solution might be a not entirely recommended hack.
In Ember, I sometimes run into the situation where I need to check if a function is returning a promise. For example, if I have a route that is derived:
MyRoute = ParentRoute.extend({
beforeModel: function() {
this._super().then(function() {
// do something...
});
}
});
But although beforeModel can return a promise, it might not. In particular, if it's the default Ember.K implementation, then it doesn't. I'd rather not always be doing:
var res = this._super();
if (typeof res.then === "function") {
res.then(function() {
// do X
});
} else {
// do X
}
I assume there's a way to wrap something that one doesn't know if it's a thenable, and then chain regardless. But I couldn't find it in the documentation.
The above is undesirable because it's verbose, and requires having the code for X twice.
Thoughts?
Update:
I was able to confirm #torazaburo's response with the following coffeescript test:
`import { test, module } from 'ember-qunit'`
module "testing promise behavior"
test "can cast to promise", ->
expect 3
order = []
returnsPromise = ->
new Ember.RSVP.Promise (resolve) ->
order.push 'a'
resolve('response 1')
returnsValue = ->
order.push 'b'
'response 2'
Ember.run ->
Ember.RSVP.resolve(returnsPromise()).then (response) ->
order.push 'c'
equal response, 'response 1'
Ember.RSVP.resolve(returnsValue()).then (response) ->
order.push 'd'
equal response, 'response 2'
equal order.join(' '), 'a b c d'
Thanks for the solution! It seems RSVP's implementation of Promises also has a built in resolve method that does what you suggest, and it turns out to be the same as cast, as you suggest, althought that is now deprecated.
There may be other better ways to do this, but you could do:
function ensurePromise(x) {
return new Ember.RSVP.Promise(function(resolve) {
resolve(x);
});
}
If x is not a promise, then this returns a promise which is already fulfilled with that value, which you can then hang then's off of. If x is a promise, then it returns a promise which assumes its status (including resolved/rejected status and value/reason).
This is equivalent in native Promises to
Promise.resolve(x)
So in your case,
MyRoute = ParentRoute.extend({
beforeModel: function() {
ensurePromise(this._super()).then(function() {
// do something...
});
}
});
Note, however, that this will potentially turn a synchronous value into an asynchronous value (a promise). However, it is generally considered bad practice to have functions that behave synchronously in some cases and asynchronously in others. So it seems OK, in the case we have a value which is potentially either synchronous or asynchronous, to coerce it into something which is always asynchronous.
I believe in some past version there used to be something called RSVP.Promise.cast, which I seem to recall did roughly the same thing, but I can't track it down now.
I have a handful of computed properties defined on a component. I'd like to refactor these computed properties to live within a messages object on the component. When I make a call to get one of the computed properties elsewhere, I'm returned an instance of Ember's ComputedProperty object, rather then the translation string I expected. Looking at the documentation, Ember.get should invoke the computed property and return the object itself, the property value or null.
What am I missing? How would I go about structuring these nested computed properties so that I can access them using the get/set interface elsewhere in the component?
App.ValidatedDateComponent = Ember.Component.extend({
format: null,
label: null,
messages: {
invalidDateMsg: (function() {
return I18n.t('%{date} must be a valid date. %{format}', {
date: this.get('label'),
format: this.get('format')
});
}).property('label', 'format')
},
validate: function(value, status) {
if (!moment(value).isValid()) {
return status(false, Ember.get(this.messages, 'invalidDateMsg'));
} else {
return this._super(value, status);
}
}
});
Ember only supports defining computed properties while extending Ember.Object class, the exception to the rule is while defining a Ember.Mixin.
Defining the top level of the nest
var nest = Ember.Object.extend({
foo: function() {
return "something";
}.property()
});
Creating an instance of it
App.IndexController = Em.ArrayController.extend({
messages: nest.create()
});
Template
{{messages.foo}}
http://emberjs.jsbin.com/UhUvOvU/1/edit
So in your case you could, if you really wanted to, do:
messages: Em.Object.extend({
invalidDateMsg: function() {
return I18n.t('%{date} must be a valid date. %{format}', {
date: this.get('label'),
format: this.get('format')
});
}.property('label', 'format')
}).create(),