Ember.js: Is it possible to inject a dependency on a specific Route/Controller Mixin? - ember.js

Let's say I have a SessionManager instance which I want to be accessible in every Route extending my ProtectedRoute Mixin, is it possible to inject this dependency into a "group of routes" as I can reference a single Route instance?
So instead of:
App.inject('route:protected1', 'sessionManager', 'session_manager:main');
App.inject('route:protected2', 'sessionManager', 'session_manager:main');
....
I could do something like
App.inject('route:protectedmixin', 'sessionManager', session_manager:main);

You certainly can, but it might involve a bit of juggling. You could define any logic to decide what to inject and where if you want to rely on the default conventions you could manually find this objects and then use the fullname when injecting.
Another option would be to do it for each route, regardless of whether they include the Mixin or not. Inject doesn't need the full name, if you call `App.inject('route', ...) it would work by default.
If going with option one, it would look something like this. You basically need to find those routes implementing their mixins and then inject into all of those.
var guidForMixin = Ember.guidFor(App.YourMixin);
var routesToInjectInto = Ember.keys(App).filter(function (key) {
var route, mixins;
if (key.match(/Route$/))
route = App[key];
mixins = Ember.meta(route).mixins;
if (mixins) {
!!mixins[guidForMixin];
}
return false;
);
routesToInjectInto.each( function (key) {
var keyForInjection = Ember.decamelize(key);
App.inject('route:' + keyForInjection, 'sessionManager', 'session_manager:main');
});
Also I would suggest doing all of this inside an initializer, but that might be a minor consideration.
Ember.onload('Ember.Application', function(Application) {
Application.initializer {
name: "sessionManager"
initialize: function (container, application) {
// do the above here. Refer to app as the namespace instead of App.
// use the container instead of App.__container__ to register.
};
});

Related

How to update nested state in Ember Octane

So my situation is as follows:
I got a component with a couple of input fields that represent a contact and are filled with data from a service:
#service('contact-data') contact;
Each field stands for one property that is accessed via
{{contact.properties.foo}}
I have the properties saved as a JS object to easily filter out empty fields when using them and I tracked it with #tracked like so:
export default class MyService extends Service {
#tracked properties = {id: 0, foo: "", bar : "abc", };
#action updateProperty(name, value) {
this.properties[name] = value;
}
}
However, the properties do not re-render properly in the component and the textfields do not get updated.
I'd appreciate any help with this! Thanks!
Any time you have a bunch of nested state like that which needs to be tracked, just tracking the top-level object won't cause updates to the internals of that object to propagate out. You need to track the internal properties, or you need to reset the whole object you're tracking.
You have basically two rough options for dealing with updates to those internal properties:
If the object has a well-known shape, extract it into a utility class which uses #tracked on the fields, and instantiate the utility class when you create the service. Then updates to those fields will update.
If the object is really being used like a hash map, then you have two variant options:
Use https://github.com/pzuraq/tracked-built-ins, if you don't need IE11 support
Do a "pure functional update", where you do something like this.properties = { ...this.properties, foo: newValue };
Of these, (1) is pretty much always going to be the cheapest and have the best performance. Doing (2.1) will be a little more expensive, because it requires the use of a Proxy, but not enough that you would normally notice. Doing (2.2) will end up triggering a re-render for every property in the properties used anywhere in the app, even if it didn't change.
In the case you've described, it appears the fields are well known, which means you should reach for that class. The solution might look something like this:
import Service from '#ember/service';
import { action } from '#ember/object';
import { tracked } from '#glimmer/tracking';
class TheProperties {
#tracked id;
#tracked foo;
#tracked bar;
}
export default class MyService extends Service {
properties = new TheProperties();
#action updateProperty(name, value) {
this.properties[name] = value;
}
}
Note that #tracked installs getters and setters in place of plain class properties, so if you need to use this for a JSON payload somewhere or similar, you'll also want to implement toJSON on the utility class:
class TheProperties {
#tracked id;
#tracked foo;
#tracked bar;
toJSON() {
let { id, foo, bar } = this;
return { id, foo, bar };
}
}
There's another add-on that does basically the same thing for Array and Objects as tracked-built-ins.
It's a proxy that basically notifies the root that an update has occurred somewhere. The advantage against tracked-built-ins is that the nesting depth is not limited as it's common for JSON to have deep nesting.
The drawbacks are similar to tracked-built-ins in terms of performance. Use it sparingly and try not to use it in tables with hundreds/thousands of rows as re-rendering is going to be not performant.

Is it good practice to reference services in html templates in Angular 2?

As the question states, is there any downside in referencing the service directly in the template as such :
[disabled]="stateService.selectedClient == null || stateService.currentStep == 1"
In my opinion this doesn't seem like good practice and I'd much rather keep a "selectedClient" object in whatever component needs to use it. How can I get the state and store it into local variables, while observing the changes:
example: I want to move from step1 to step2 by changing "currentStep" in the "stateService", however I want the component that keeps "currentStep" ALSO as a local variable to reflect the change in the state?
Is it good practice to reference services in html templates in Angular
2?
I'd generally avoid it. It seems to bring more chaos than good.
Cons:
Coming from OOP background, this approach looks like it breaks the Law of Demeter, but more importantly,
It's no longer MVC, where your controller (Angular2's Component) acts like a mediator between the view and the services.
Like Ced said, what if a call to a service's member is costly and we need to refer to it multiple times in the view?
At the moment my editor of choice (VS Code) does not fully support Angular2 templates; referencing too many things outside of its own Component's scope in a template makes refactoring not fun anymore.
Pros:
Sometimes it looks more elegant (because it saves you 2 lines of code), but trust me, it's not.
How can I get the state and store it into local variables, while
observing the changes
Madhu Ranjan has a good answer to this. I'll just try to make it more complete here for your particular example:
In your StateService, define:
currentStep : Subject<number> = new Subject<number>();
selectedClient: Subject<Client> = new Subject<Client>();
changeStep(nextStep: number){
this.currentStep.next(nextStep);
}
selectClient(client: Client) {
this.selectedClient.next(client);
}
In your Component:
currentStep: number;
constructor(stateService : StateService){
stateService.currentStep.combineLatest(
stateService.selectedClient,
(currStep, client) => {
if (client == null) {
// I'm assuming you are not showing any step here, replace it with your logic
return -1;
}
return currStep;
})
.subscribe(val => {
this.currentStep = val;
});
}
You may try below,
stateService
currentStep : Subject<number> = new Subject<number>();
somestepChangeMethod(){
this.currentStep.next(<set step here to depending on your logic>);
}
component
// use this in template
currentStep: number;
constructor(stateService : stateServiceClass){
stateService.currentStep.subscribe(val => {
this.currentStep = val;
});
}
Hope this helps!!
It is probably not a good idea to expose your subject inside of your state service. Something like this would be better.
StateService
private currentStep: Subject<number> = new Subject<number>();
changeStep(value: number) {
this.currentStep.next(value);
}
get theCurrentStep(): Observable<number> {
this.currentStep.asObservable();
}
Component
currentStep: number;
constructor(private stateService: StateService) {
this.currentStep = this.stateService.theCurrentStep;
}
Template
[disabled]="(currentStep | async) == 1" // Not sure if this part would work

Loopback include unrelated lists

In Loopback it is easy to include relational objects when querying for data. For example, one can include all the comments that belong to a blog post in a single call using the include filter.
But in my case I want to get data that doesn't have a relation.
I have a User Detail page. On that page a user can choose a username and there's also a dropdown list where a user can choose from what country he is.
So from the client side I do something like:
Country.find().$promise.then(function(countryData) {
$scope.countries = countryData;
});
Player.find().$promise.then(function(playerData) {
$scope.player = playerData;
}
But what if I get more lists that I want to fill? Like, city, state, colors etc.
Then I'd have to make a lot of separate calls.
Is there a way to include all this data in one call, eventhough they have no relation? Something like this:
Player.find({ filter: { include: ["countries", "colors"] } }).$promise.then(function(data) {
// some stuff
}
You may want to try using the Where filter as documented here
An example of this for querying two specific things would be:
Post.find({where: {and: [{title: 'My Post'}, {content: 'Hello'}]}},
function (err, posts) {
...
});
You could create a remote method on one of your models that makes the calls internally and packages them back up for you.
Use some promise library if not using ES6 to wait for all and then return
Model.getAll = function(next) {
var promises = [];
promises.push(Model.app.models.Country.find());
promises.push(Model.app.models.Player.find());
promises.push(Model.app.models.Color.find());
Promise.all(promises)
.then(function(results) {
next(results);
});
}
/**
Register your remote method here
*/
You could create a remote method on one of your models that makes the calls internally and packages them back up for you.
Use some promise library if not using ES6 to wait for all and then return
Model.getAll = function(next) {
var promises = [];
promises.push(Model.app.models.Country.find());
promises.push(Model.app.models.Player.find());
promises.push(Model.app.models.Color.find());
Promise.all(promises)
.then(function(results) {
next(results);
});
}
/**
Register your remote method here
*/
I have problem and try with this solution but i get error "Failed with multiple errors, see details for more information.". It seems like there is bug on Loopback while using promise.all

Direction-aware transition with Ember Liquid Fire's {{liquid-with}} helper

In my Ember app, I would like to make nice Liquid Fire transitions within the same route when the model of a dynamic route changes. Here's my router:
// app/router.js
Router.map(function() {
this.route("campaigns", { path: "/" }, function() {
this.route("campaign", { path: "/campaign/:election_year" });
});
});
I would like the view to leave the screen to the left when switching to an election_year that is in the future (e.g., from campaign/2008 to campaign/2012) and to the right the other way around.
My first thought was to use use the {{liquid-outlet}} and the toModel function in the app/transitions.js file, but Edward Faulkner (creator of Liquid Fire) says in this thread that
liquid-outlet does not handle model-to-model transitions on the same
route
and that I should use {{liquid-with}} instead. However I'm at a loss as to how to handle the directionality, i.e. make the view go left when election_year increases, and go right when it decreases. Any clues?
When using a liquid-with (or liquid-bind) helper, there is not necessarily a route or model involved in the animation, and so constraints like toRoute or toModel do not apply. Instead, you can use toValue which will match against whatever you're passing into the helper.
Assuming you're passing a Campaign model to liquid-with, you could use a rule like this in transitions.js:
this.transition(
this.toValue(function(newValue, oldValue) {
return newValue instanceof Campaign &&
oldValue instanceof Campaign &&
newValue.electionYear > oldValue.electionYear
}),
this.use('toLeft'),
this.reverse('toRight')
)
Explanation:
We have a constraint, an animation to use when it matches (toLeft), and an animation to use when it matches with "to" and "from" reversed (toRight),
All the rule constraints (including toValue, fromRoute, etc) can accept:
simple values like toValue(42) or toRoute('posts') that will be compared with ===
regular expressions like toRoute(/unicorn/)
functions that test the value, like toValue(n => n > 1).
functions that compare the value and the "other value", like
toValue((newThing, oldThing) => newThing.betterThan(oldThing))
The final case is what we're using in the solution above.
(We are continuing to refine these APIs. In this particular case, I think we should add an explicit name for the comparison rule, like this.compareValues(function(oldValue, newValue){...}), instead of just overloading toValue to also do comparison. But this should not affect your solution, as I'm not going to break the existing behavior in the foreseeable future.)

Force a controller to always act as a proxy to a model in Ember

I'm looping through a content of an ArrayController whose content is set to a RecordArray. Each record is DS.Model, say Client
{{# each item in controller}}
{{item.balance}}
{{/each}}
balance is a property of the Client model and a call to item.balance will fetch the property from the model directly. I want to apply some formatting to balance to display in a money format. The easy way to do this is to add a computed property, balanceMoney, to the Client object and do the formatting there:
App.Client = DS.Model({
balance: DS.attr('balance'),
balanceMoney: function() {
// format the balance property
return Money.format(this.get('balance');
}.property('balance')
});
This serves well the purpose, the right place for balanceMoney computed property though, is the client controller rather than the client model. I was under the impression that Ember lookup properties in the controller first and then tries to retrieve them in the model if nothing has been found. None of this happen here though, a call to item.balanceMoney will just be ignored and will never reach the controller.
Is it possible to configure somehow a controller to act always as a proxy to the model in all circumstances.
UPDATE - Using the latest version from emberjs master repository you can configure the array controller to resolve records' methods through a controller proxy by overriding the lookupItemController method in the ArrayController. The method should return the name of the controller without the 'controller' suffix i.e. client instead of clientController. Merely setting the itemControllerClass property in the array controller doesn't seem to work for the moment.
lookupItemController: function( object ) {
return 'client';
},
This was recently added to master: https://github.com/emberjs/ember.js/commit/2a75cacc30c8d02acc83094b47ae8a6900c0975b
As of this writing it is not in any released versions. It will mostly likely be part of 1.0.0.pre.3.
If you're only after formatting, another possibility is to make a handlebars helper. You could implement your own {{formatMoney item.balance}} helper, for instance.
For something more general, I made this one to wrap an sprintf implementation (pick one of several out there):
Ember.Handlebars.registerHelper('sprintf', function (/*arbitrary number of arguments*/) {
var options = arguments[arguments.length - 1],
fmtStr = arguments[0],
params = Array.prototype.slice.call(arguments, 1, -1);
for (var i = 0; i < params.length; i++) {
params[i] = this.get(params[i]);
}
return vsprintf(fmtStr, params);
});
And then you can do {{sprintf "$%.2f" item.balance}}.
However, the solution #luke-melia gave will be far more flexible--for example letting you calculate a balance in the controller, as opposed to simply formatting a single value.
EDIT:
A caveat I should have mentioned because it's not obvious: the above solution does not create a bound handlebars helper, so changes to the underlying model value won't be reflected. There's supposed to be a registerBoundHelper already committed to Ember.js which would fix this, but that too is not released yet.