I wanted to understand if the needs attribute could be used to inject arbitrary objects into controllers, routes and views.
I am developing an Ember.js application where I am writing a custom data-service layer that communicates with the backend to load and persist data. I define Ember Objects which represent the various backend services such as:
App.SessionServiceClient = Em.Object.extend({
// methods and attributes
});
App.UserServiceClient = Em.Object.extend({
// methods and attributes
});
I now register these objects with the application's container to make them available for DI:
App.register('service:session', App.SessionServiceClient, {singleton: false});
App.register('service:user', App.UserServiceClient, {singleton: false});
Now that the objects are available for injection, and if I have a controller that needs only the SessionServiceClient, can I do the following:
App.SignInController = Em.ObjectController.extend({
needs: ['service:user'], // using the needs to declare dependency
actions: {
// actions for the view
}
});
When i tried this, it did not work. Is this possible with Ember.js or am I doing it wrong?
Best practice is to use initializers to inject your service into the controllers you need it in. SEe http://ember.zone/ember-application-initializers/
Ember.Application.initializer({
name: "sessionLoader",
after: "store",
initialize: function(container, application) {
container.register('service:session', App.SessionServiceClient, {singleton: false});
container.injection('route', 'session', 'service:session');
container.injection('controller', 'session', 'service:session');
});
}
});
Also you should really try to switch to Ember-CLI or at least use an ES6 module structure. (i.e. use imports instead of globals.)
Related
I need to access the application specific data in my components as well as routes. I also need to set the application specific data from normal JS.
I have currently created an object with global namespaces (App.globalSetting) and then created the variables as properties on this object.
I am then able to set and get the variable using App.globalSetting.set() and App.globalSetting.get().
Is the above method a good practice or is there a better way to do the same.
Also the data to be stored is critical. So please suggest a best way to accomplish this task.
You may want to take a look at Services: http://guides.emberjs.com/v2.0.0/services/.
From the guides:
"An Ember.Service is a long-lived Ember object that can be injected as needed."
Example service:
settings-service.js
export default Ember.Service.extend({
exampleSetting: true,
update(value) {
this.set('exampleSetting', value);
}
});
How to access this service from a Component (or Route):
export default Ember.Component.extend({
settings: Ember.inject.service('settings-service'),
actions: {
doSomething(val) {
this.get('settings').update(val);
}
}
});
I need to load contents coming from my API when the application boots, then inject what I get into all routes and controllers to be able to access them whenever I want.
I was wondering where is the best place to do that in Ember?
In an initializer? I've heard that it's not a good practise to use the store from there...
In the application route? Then how can I access it from all routes and controllers? Using this.modelFor('application')? Is that a good practise?
Thanks.
The best place would be in the ApplicationRoute, you can do this in the beforeModel or afterModel/setupController hook as you like. Here's a beforeModel example:
ApplicationController = Ember.ObjectController.extend({
beforeModel:function() {
var self = this;
var rsvp = Ember.RSVP.hash({
fruits: self.store.find('fruit'),
candies: self.store.find('candy'),
meats: self.store.find('meats')
}};
rsvp.then(function(models) {
self.controllerFor('fruits').set('model',models.fruits);
self.controllerFor('candies').set('model',models.candies);
self.controllerFor('meats').set('model',models.meats);
});
}
});
The rsvp fetches all of the models together and waits for them to precede before continuing which happens on the then. We then assign all the found models to the model property on their matching controller.
To do this in the afterModel hook, it would look different.
I am trying to access one of two models in a controller that uses needs on a sibling controller. My router looks like the following:
App.Router.map(function() {
this.route('login');
this.route('mlb.lineups', {path: 'tools/mlb/lineups'})
this.resource('mlb.lineups.site', { path: 'tools/mlb/lineups/site/:site_id' });
});
The mlb.lineups route definition looks like the following:
App.MlbLineupsRoute = Ember.Route.extend({
model: function() {
var self = this;
return Ember.RSVP.hash({
sites: self.store.find('site')
})
},
setupController: function(controller, models) {
controller.set('model', models.get('sites'));
},
afterModel: function(models) {
var site = models.sites.get('firstObject');
this.transitionTo('mlb.lineups.site', site);
}
});
The reason I am using Ember.RSVP.hash({}) here is I plan on adding another model to be retrieved after I retrieve the site model.
Now in my MlbLineupsSiteController I am trying to access the sites model with the following:
App.MlbLineupsSiteController = Ember.ArrayController.extend({
needs: "mlb.lineups",
sites: Ember.computed.alias("controllers.models.sites")
});
This is the error I'm getting in my Ember console: needs must not specify dependencies with periods in their names (mlb.lineups)
What's the best way to make the sites model from the MlbLineups controller available in my MlbLineupsSiteController?
Note:
#NicholasJohn16's answer isn't valid anymore. It always gives an error that controller couldn't be found. Generally you should also never use needs property and always use Ember.inject.controller if you have to make your controllers dependent on each other. I'd also recommend using services instead of dependencies between controllers. It's easier to maintain code which contains communication between controllers through services, than controller directly accessing other controller's properties. You might not always be aware of such access, and using services gives you another layer of security.
Solution:
Tested in Ember.js 1.10.0-beta.4. Use following code in Controller to reference nested controller in needs:
needs: ['classic/about']
Then you can access it later using:
const aboutController = this.get('controllers.classic/about');
const aboutProperty = aboutController.get('customProperty');
Works as expected. Basically you need to replace dots with slashes.
It should be:
needs:" MlbLineupsSite "
Basically, the name of the controller you want to include, minus the word controller.
Everything else you posted should work.
I am using angular-js and I am looking for a way to model my data. I am inspired by Backbone and started to write something similar to Backbone model. In Backbone model there are get() and set() methods for accessing model's members.
What is the advantages of using get() and set() methods over using model.attributes.__?
If I don't need my angular models to trigger changes events, does it worth to create get() and set() methods that are similar to Backbone.Model's get() and set()?
EDIT
To be more clear, I am asking if there is advantage to make a set() and get() methods over direct access to the fields. If there is no any advantage, why does Backbone people decided to go that way?
You just need to create a service which would hold the data, you can think of services like instances of models or collections. For example, you have some domain class like Book, what you can do is to create a service 'BookListService' which would be responsible for CRUD and/or business logic.
Something like this
function Book(title, author){
this.title = title;
this.author = author;
this.isRead = false;
}
Book.prototype.markAsRead = function(){
this.isRead = true
}
angular.module('someModule', []).factory('BookListService', function($http){
var service {
books: [],
load: function(){
$http.get('/books').then(function(res){
angular.forEach(res.data, function(book){
service.books.push(new Book(book.title, book.author))
})
})
}
};
return service;
})
Angular's 'models' are any object in javascript.
If you check out the tutorials on angularjs.org,
http://jsfiddle.net/api/post/library/pure/
The data is defined in the Controller. So make an array, just do
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
To get/set is just normal javascript:
get length: $scope.todos.length;
set to empty: $scope.todos = [];
In the view (html), just call the variable if its under the same Controller
<div ng-controller="TodoCtrl">
<span>{{todos.length}}</span>
</div>
I had some trouble with this at first because Angular seems overwhelming, so I was expecting a big answer for model implementation. The answer is simply to use JS objects. Angular has all of the tools you need to easily create a model yourself without the use of Backbone (which I have also read is not really the way to go with Angular). If you look into $emit, $broadcast, $on, you have your events for model updates. If you want to create instances of the objects rather than singletons, then have your factory return an instance. A good place to start I think is angular-app. Apologies if this isn't specific enough.
If you are looking for a package to use, try BreezeJS.
Let's say I declared an application namespace:
App = Ember.Application.create();
and later I write an arrayController instance that creates objects and hook it onto the app namespace on user event:
App.objController = Ember.ArrayController.create({
content: [],
createObj: function(){
// instantiate new object
var newObj = Ember.Object.create({ ... })
//give obj a name
var newObjName = this._getObjName( someParam );
// hook object to an app namespace -> this is where I have an issue
App[newObjName] = newObj
},
...
});
See I explicitly use App[newObjName] = newObj to hook the object onto the namespace, ideally I would like some sort of generic way to name the application namespace in case I use the objController for a different application later.
There has to be some way to do this though I am just not familiar enough with Ember to have encountered it.
Note: on a scale of 1 to JFGI, this question is definitely not a 1. On the other hand it's a free resolved checkmark for anyone that has a moment.
During the initialization phase, Ember will instantiate all of your controllers and inject three properties into each of them - "target", "controllers", "namespace". The "namespace" property is your application.
That said, instead of hard-coding the top-level object:
App[newObjName] = newObj
you can do the following:
this.get("namespace").set(newObjName, newObj);
Note - in order for this to work, your application needs a router. Also, you should define controller classes, not instances. Ember will instantiate all controllers for you. So, this
App.objController = Ember.ArrayController.create({/* code here */});
should be written as
App.ObjController = Ember.ArrayController.extend({/* code here */});
Note the capital "O" in "ObjController".
Consider using injections, which is the preferred way to add dependencies.
Ember.Application.registerInjection({
name: 'fooObject',
before: 'controllers',
injection: function(app, router, property) {
if (property === 'FooObject') {
app.set('fooObject', app[property].create());
}
}
});
So if you define a class as follows:
App.FooObject = Ember.Object.extend({
// ...
});
the injection will create an instance into App.fooObject. Although we still use the namespace App, however only once. You could further do:
Ember.FooObject = Ember.Object.extend({
// ...
});
and then in your App, App.FooObject = Ember.FooObject but I'm not sure if its useful.