ng-model versus getElementById - angularjs

I am reading the book "Pro AngularJS" by Adam Freeman, Apress.
He creates an app that is a to-do list and you can add your own things on the list. In the part "Responding to User Interaction", page 34
Here is the model
var model = {
user: "Adam",
items: [{ action: "Buy Flowers", done: false },
{ action: "Get Shoes", done: false },
{ action: "Collect Tickets", done: true },
{ action: "Call Joe", done: false }]
};
the app
var todoApp = angular.module("todoApp", []);
the controller
todoApp.controller("ToDoCtrl", function ($scope) {
$scope.todo = model;
$scope.addNewItem = function (actionText) {
$scope.todo.items.push({ action: actionText, done: false });
}
});
the input field
<input class="form-control" ng-model="actionText" />
and the button that adds new things in the model
<button ng-click="addNewItem(actionText)">Add</button>
And then he comments this line <input class="form-control" ng-model="actionText" /> (pp 36)
I have specified the name of a property for the directive to update
that is not part of the model. The ng-model directive will dynamically
create the property for me within the scope of the controller,
effectively creating dynamic model properties that are used to handle
user input.
What does he mean by
creating dynamic model properties that are used to
handle user input.
?
Do we add a new property to the model named actionText ? . We add a ""pseudo""-property so the controller can use it to grab an input value?
If this is the case, then why use ng-model="actionText" and not just use plain old getElementById? After all we only need the value of the field.
Can something like ng-click getElementById("myInput").value do the trick and also avoid adding unwanted stuff to the model?
Thanks

One of the features of angularjs is to abstract as much as possible the manual javascript manipulation of the DOM.
Imagine having 10 fields on a form and having to do getElementById…..It’s much simpler in angularjs. You just need to have ng-model in each input type and on your controller methods you just use your model. You don't even have to pass them as arguments; the method on the controller will have access to the model.
I think that using ng-model will ultimately generate a cleaner code than using getElementById to access each field value.

Related

Angularjs conditional binding

I have used angular's ng-model for quite some time which demonstrates two way data binding. What i want to accomplish is to bind only an input field to a model only if there are changes.
If I have
<input value="Hello world">
I want the value to be propagated to a model variable only if there are changes made to the value.
Answer would depend on event you want to use to update model.
Assuming you are wanting an "edit form " but don't want the master model to update live you can make a copy of the model and extend the master on "save"
Starting data:
$scope.item ={age: 25, name: 'Foo Bar'};
$scope.editItem = angular.copy($scope.item);
HTML
<input ng-model="editItem.age">
<button ng-click="updateItem()">Update</button>
Update function:
$scope.updateItem = function (){
$http.put(url, $scope.editItem).success(function(resp){
// merge data
angular.extend( $scope.item, $scope.editItem);
});
}
You could also do something similar using ng-change
You can do it with using of additional variable and $watch. Example:
<input type="search" ng-model="searchText">
And in controller
$scope.$watch('searchText', function() {
$scope.filterText = $scope.searchText;
});
So $scope.filterText will be changed to $scope.searchText value if any changes in input

Form naming and model naming recommendation

If I have a form like this
<form name="form" ng-submit="form.save()">
<input type="text" name="name" ng-model="form.name">
<button type="submit" ng-disabled="form.name.$pristine">Submit</button>
</form>
Here I have a form named "form", an input-field named "name" and a model named "form.name". Will the formname+fieldname ("form.name") be in conflict with the model name ("form.name") somehow? Is this bad naming practice? Or maybe this is perfectly OK and will give me no troubles?
To be honest that's a terrible Idea, naming your form the same your model. When you declare a form in Angular, a form object by the name of the form will be created on the Scope of the view's controller. Having a model object named same as the form will result in in mixing up of the 2 objects, the form object created by angular has many fields and functions of it's own, why would you want to litter your model with that? If you try running the code you posted and placing a $watch on form, you will notice that the model value won't even be bond properly:
$scope.$watch("form", function (v) {
console.log(v);
}, true);
results in:
$addControl: function (control) {
$dirty: true
$error: Object
$invalid: false
$name: "form"
$pristine: false
$removeControl: function (control) {
$setDirty: function () {
$setPristine: function () {
$setValidity: function (validationToken, isValid, control) {
$valid: true
name: "[object Object]john"
Solution: Just give your model and form different names :)

Angular model binding with MVC Html.TextBoxFOr

I have controls that are model tied to ASP.net MVC5
#Html.TextBoxFor(model => model.OriginLocation.City, new { #class = "form-control", data_ng_model = "address1.City", test_change = "" })
So when the page loads the value of text box input is bound and should display value coming from service with Razor bound controls, latter i can manipulate that value which changes angular model for this control.
What i have is textbox loads empty.
I can see the value when I view source but its not displayed.
<input class="form-control ng-pristine ng-valid" data-ng-model="address1.City" data-val="true" data-val-length="The field City must be a string with a maximum length of 50." data-val-length-max="50" id="OriginLocation_City" name="OriginLocation.City" test-change="" type="text" value="Manheim">
js fragment
app.controller('LocationCtrl', ["$scope",
function ($scope) {
$scope.address1 = { Label: 'address1' };
ngModel has precedence over the value that is originally set (it's setting the value to "" because the model doesn't exist). Take a look here...
http://jsfiddle.net/yApeP/
But you can specify a value using ngInit...
http://jsfiddle.net/D7vh7/
Which means you can use ngInit when generating the textbox...
#Html.TextBoxFor(model => model.OriginLocation.City,
new { #class = "form-control",
data_ng_model = "address1.City",
test_change = "",
data_ng_init = string.Format("address1.City = '{0}'", Model.OriginLocation.City.Replace("'", #"\'")) })
Angular will replace the contents of the text box with the value from its own model, which means you need to populate the Angular model. This can be achieved quickly by serializing your MVC model (or part of it) into your Angular model:
app.controller("TestController", function($scope) {
$scope.person = #Html.Raw(JsonConvert.SerializeObject(Model.Person));
});
You can then render your MVC controls in the view like this:
#Html.TextBoxFor(x => x.Person.Name, new { ng_model = "person.Name" })
Considering the answer by #Alan, I found a way that is "kind of" clean. Well that is the solution that I am using when creating ASP MVC Projects with Angular.js.
Instead of serializing the model inside the Angular controller (which would require to declare the controller inside of the Razor template), I serialize it in a global variable in each of my razor template like :
<script>
//Add the namespace you want to not poluate the global namespace
window.Model = #Html.Raw(JsonConvert.SerializeObject(Model))
</script>
I also use the property attribute [ScriptIgnore] for the properties I don't want to serialize (that could cause circular references).
public class Classroom {
public string Title {get;set;}
[ScriptIgnore]
//Students could be associated to classroom, and that classroom has
// students etc. If you want those properties, create a new object in a
// PageView withtout circular references
public List<Students> Students {get;set;}
}
After that, inside of my controllers, I simply do something like
app.controller("TestController", function($scope) {
$scope.model = window.Model;
//My other stuff...
});
Also, that way your angular Model always have the SAME properties has your ASP MVC Model.
One clean way to solve this life-cycle issue is by writing a directive for the "value" attribute for the input.
At first you can simply write the Razor's model value in yout input element by using the basic Razor syntax.
<input type="text" class="form-control" id="name" name="name" value="#Model.Name" data-ng-model="myModel.name">
That will write the value of the #Model.Name in the input when the page is rendered, but this value will be replaced by empty values when Angular starts the bindings.
So you can write a directive, and on the "link" method you can play with the ngModel in order to keep the value that was present in the input before Angular cleans it.
(The property 'prioriy: 0' indicates that the directive should be compiled as soon as possible)
function inputDirective(){
return {
restrict:'A',
priority: 0,
require: 'ngModel',
link: function(scope, elem, attrs, ngModelCtrl){
ngModelCtrl.$setViewValue($(elem).val());
}
};
}
That should restore the value written by Razor in your Angular's model

Backbone.js per attribute rendering (multiple small views vs multiple templates per view )

I have a model and a view. The view displays attributes of a model and allows the user to manipulate these attributes. The problem is that when an attribute is modified it re-renders the whole view which causes a lot of problems for me.
Example blur event on a text input saves the new input to an attribute and thus fires render. Which means that if the user clicked from that text input straight to a button on the same view that event will never fire as the first event that fires will be blur causing the whole view to re-render and thus losing the button click event.
I have two ideas:
Have a single view where every attribute is in a separate template. Then I bind to a particular attribute change event and in render I update only the html of the changed attribute. This seems like a hack, as there is a lot of work to force the view to update only the changed attribute. It will add a lot of unnecessary complexity to an already complex view.
Create a master view which consists of views, where each of them represents a model's attribute. This will create a lot of views, with nearly no functionality.
I seem to prefer the 2. option. What do you think? What are the best practices? Is there any better way to handle this?
I think you can do this quite easily.
Take a step back and think about where you are binding your events. It seems that you are binding them directly on top of each individual element instead of using a parent delegate.
Here's an example
Backbone.View.extend({
el: $("div.parent"),
events: function() {
this.$el.on("click", "input[type=button]", function(){});
// jquery cross browser on this
this.$el.on("blur", "input[type=text]", function(){});
},
initialize: function() {
this.model.bind("change", this.render, this);
},
render: function() {
this.$el.html('<input type="text" /><input type="button" />');
}
});
Here's what el and it's structure looks like
<div class="parent">
<input type="text" />
<input type="button" />
</div>
So this.$el points to div.parent. I can constantly rerender the contents of this.$el, and as long as the html structure dosen't change, I don't have to worry about events getting unbound. The other solution is that if I really cannot do delegation, I would just call the events method whenever I render again.
Like you said yourself, both of your options seem very complex. But sometimes additionaly complexity is a necessary evil. However, if the updated fields are something relatively simple (like binding a value to an element or an input field), I would simply update the DOM elements without creating additional View/Template abstractions on top of them.
Say you have a model:
var person = new Person({ firstName: 'John', lastName: 'Lennon', instrument:'Guitar' });
And a view which renders the following template:
<div>First Name: <span class="firstName">{{firstName}}</span></div>
<div>Last Name: <span class="lastName">{{lastName}}</span></div>
<div>Instrument: <input class="instrument" value="{{instrument}}"></input></div>
You could declare in the view which property change should update which element, and bind the model change event to a function which updates them:
var PersonView = Backbone.View.extend({
//convention: propertyName+"Changed"
//specify handler as map of selector->method or a function.
firstNameChanged: { '.firstName': 'text' },
lastNameChanged: { '.lastName': 'text' },
instrumentChanged: { '.instrument': 'val' },
otherFieldChanged: function(val) { //do something else },
initialize: function (opts) {
this.model.on('change', this.update, this);
},
//called when change event is fired
update: function(state) {
_.each(state.changed, function(val, key) {
var handler = this[key + "Changed"];
//handler specified for property?
if(handler) {
//if its a function execute it
if(_.isFunction(handler)) {
handler(val);
//if its an object assume it's a selector->method map
} else if(_.isObject(handler)) {
_.each(handler, function(prop, selector) {
this.$(selector)[prop](val);
}, this);
}
}
}, this);
}
A solution like this doesn't scale to very complex views, because you have to add classed elements to the DOM and maintain them in the View code. But for simpler cases this might work quite well.
In addition it's always good to try to compose views of multiple, smaller views, if they naturally divide into sections. That way you can avoid the need to update single fields separately.

Select New DOM elements created by Angularjs ng-repeat

I am using Angularjs for a web application. I have tried searching to find a solution to my problem, and it seems that Angularjs do not facilitate an easy way to access newly created DOM elements within ng-Repeat.
I have prepared a jsfiddle to show the actual problem.
here is the link: http://jsfiddle.net/ADukg/956/
Please let me know how to select the new DOM element within ng-repeat.
To expand on my comment, I have updated your fiddle to show a simple implentation of a directive that alerts the class of the element.
http://jsfiddle.net/ADukg/994/
Original comment:
Regarding dom manipulation in the controller, it says here that you should not do it at all. It should go in a directive. The controller should only contain business logic.
Why it doesn't work, I don't know, but it's probably because angular is still running its own stuff at this point in time.
There are two ways to do this:
1 ng-init
function controller($scope) {
$scope.items = [{id: 1, name: 'one'}, {id: 2, name: 'two'}];
$scope.initRepeaterItem(index, item) {
console.log('new repeater item at index '+index+':', item);
}
}
<ul>
<li ng-repeat="item in items" ng-init="initRepeaterItem($index, item)"></li>
</ul>
2 MutationObserver slightly more complex
Do this inside a directive, on the element whose parent gets new children (<ul> in this case)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// you get notified about DOM mutations here
// check mutation.type if it was an insertion
console.log(mutation.target.nodeName, mutation.type, mutation);
});
});
var config = {childList: true, attributes: false, characterData: false,
subtree: false, attributeOldValue: false, characterDataOldValue: false};
observer.observe(element[0], config);
Demo http://plnkr.co/h6kTtq
Documentation https://developer.mozilla.org/en/docs/Web/API/MutationObserver
Browser support http://caniuse.com/mutationobserver

Resources