Angular uses dirty checking to catch the data change and accordingly change the model or view. Ember sets observers to each elements to do this process.
But how does backbone do it?
Backbone doesn't handle data changes for updating views (by default).
Let's see.
Backbone.Model has all the methods of Backbone.Events. So when the data changes, Model calls this.trigger() function.
But you should subscribe on "change" event to handle all data manipulations:
myModel.on('change:attributeName', myView.render, myView);
Maybe it would be better to say that Backbone uses setters and Backbone.Events to observe data changes.
As #Leestex answered, backbone doesn't bind data changes for you.
but, it's better to use listenTo inside of the view. eg:
MyView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, 'change', this.render);
}
});
see:
http://backbonejs.org/#Events-listenTo
Another way for the view to listen to model events is to attach a handler to the model, like this:
this.model.on('change', this.render);
But it's generally not recommended because you would have to detach the handlers manually using off(). When you're listening to multiple models, this can get messy and may lead to
zombie views.
The benefit of using listenTo() is that you can call stopListening(), which will unbind the callbacks associated with that object. In this case, that object is the view.
Related
What's the purpose of using _.bindAll(this, 'render'); in the initialize function of a Backbone view?
It's binding your functions to the right context, the context of your view. So you are sure you're not calling it by mistake in a different context.
See this : When do I need to use _.bindAll() in Backbone.js?
My Backbone.View looks this way:
define(["promise!table_config", "BBModel"], function (config, myModel) {
"use strict";
return Backbone.View.extend({
initialize: function () {
this.model = new myModel({
foo: config
});
...
},
render: function () {
...
}
});
});
Is is a good or bad practice to initialise a model inside a view?
Particularly in this case, in a require.js module where 'config' is a require-promise, motivating me to put put a model inside a view.
While Backbone does have a Model and a View class, it is not a strict MVC framework (eg. it lacks a Controller class). The Backbone documentation page explains as much (emphasis mine):
How does Backbone relate to "traditional" MVC?
Different implementations of the Model-View-Controller pattern tend to disagree
about the definition of a controller. If it helps any, in Backbone,
the View class can also be thought of as a kind of controller,
dispatching events that originate from the UI, with the HTML template
serving as the true view. We call it a View because it represents a
logical chunk of UI, responsible for the contents of a single DOM
element.
Given that, even though (as #Evgenly mentioned in the comments) "its not view responsibility to instance model, its controllers task" ... since the Backbone View is (conceptually) a controller, it absolutely makes sense to create your models inside your views.
But putting that theory aside, here's a more practical answer. I work on a 3+ year-old Backbone app, along with two other developers (and more previously). In that app the vast majority of all models get created inside views (the few remaining ones get created inside routes). Not only has this not been a problem for us, but I can't even imagine any other way of doing it.
I'd like to call a method in my view when its attributes property is updated. The following code gives me an error: undefined is not a function.
SimpleView = Backbone.View.extend({
initialize: function(){
this.attributes = _.extend(this.attributes, Backbone.Events); // update
this.attributes.on('change', this.updateAttributes(), this);
}
});
How can I elegantly bind an event listener to the attributes?
JSFiddle here
UPDATE: I figured I have to extend attributes with Backbone.Events so I can listen to changes. Yeah... So now I don't get anymore errors, but still nothing happens. Any help would be largely appreciated.
Although I would recommend you to use a model for holding the attributes, if you want to stay with your current approach, you need to manually trigger the event.
this.attributes.trigger('change');
I have updated your JSFiddle so you can get an idea of how it works.
I would like to subscribe to an ngChange event, but from code rather than the markup. That is, given a $scope and an expression that is bindable via ngModel, I want to subscribe to any changes made to that expression by any ngModel directive that binds to that expression. Is this possible?
something like:
$scope.field = "hello";
$scope.onButtonClick = function() {
$scope.field = "button clicked!";
}
// this callback is only when the user types in an input bound to field
// not when they click the button with ng-click="onButtonClick()"
$scope.$watchNgChange("field", function() {
console.log("user caused field to change via a binding");
});
// this callback is called for both ngModel binding changes and onButtonClick.
$scope.$watch("field", function() {
console.log("field was changed");
});
I can't just use $watch, because that will capture all changes, including those from loading the data from the database, from ng-click callbacks, and changes initiated from $watch callbacks for other expressions (in this case, if there are any circular references, then it's too easy to have $watch callbacks to get into an infinite loop and error out after 10 digest cycles), and who knows what else.
First, anytime I have tried to do something like this is turned out to be a bad idea - I was just working around design problems in my code or logic problems in my business logic. In general, the code should not care HOW the data was changed, only that it HAS changed.
Second, $watch can give you both the old and new value - this has been enough for me. if the old value not equal to the new value, I want to update the related data model(s). If that old and new value are equal, I want to ignore the update.
Finally, You may consider using resolve with your routes eliminate "database loading" as the fully located data can be passed into your controller (assuming you return a promise).
.
Jeremy you just describe what in Angular is known as a Directive. It is best practice to always use directives each time you need to touch the DOM. This logic should never live in the controller or even the Service.
directives are a big tricky but there is tons of documentation for it.
Visit docs.angularjs.org/directives
Don't do it, it sounds like you are trying to introduce the DOM logic (e.g. if the user has interacted with a DOM element) into the controller.
If you read the source code of a ngChange directive, you will found it requires a ngModel which is used as the bridge between the view and the controller.
I recommend creating a copy of the model and used the copy for data binding using ngModel+ngChange in your view, and then you can $watch that copy and do whatever you want.
$scope.field = "hello"; //the field you care
$scope.fieldCopy = $scope.field; //use 'fieldCopy' for databinding
In the html code you can have multiple way of changing the model fieldCopy
<input ngModel="fieldCopy" name='foo' />
<input ngModel="fieldCopy" name='foo2' />
You then watch the fieldCopy for changes related to user interaction and copy the change to 'field':
$scope.$watch("fieldCopy", function() {
$scope.field = $scope.fieldCopy;
console.log("user caused field to change via a binding");
});
If you want to keep fieldCopy in sync with field, add another watch:
$scope.$watch("field", function() {
$scope.fieldCopy = $scope.field;
});
Is there a way to call a common controller everytime ng-view is changed? i.e i want a common controller to be called everytime a new $route is loaded.
If you have specified custom controllers for your different routes, then there's no way that I know of that you can also specify a common controller that always gets invoked, unless you use some kind of inheritance and always call a method in the base controller.
An alternative approach is to subscribe to the events the route service broadcasts.
Example:
function MyController($rootScope, [...]) {
$rootScope.$on('$routeChangeSuccess', function (current, previous) {
// ...
});
}
You have a list of available events and their parameters here.
I believe you also can add properties, methods etc. to $rootScope which you can use in bindings in your views thanks to how Angular's binding mechanism works. If it doesn't find it on the current scope, it checks its parent etc. up to the root scope.