Angular model binding with MVC Html.TextBoxFOr - angularjs

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

Related

Bind dynamically set ng-model attribute for text field

After searching around for hours I am still unable to find an answer to my problem. I am populating a dynamic form with text fields based on values from a database, but am unable to successfully bind the fields to my model. Here's the scenario:
I've got a "project" model in my controller containing lots of project related information (name, start date, participants, category etc), but let's just focus on the "project.name" property for now. In the database I configure "forms" with a number of associated fields, where each field has a property that points to which property it corresponds to in my view model (e.g. "project.name"). At runtime I add these fields to an HTML form dynamically and attempt to set the ng-model attribute to the "modelBinding" value, in this case "project.name".
<div ng-repeat="formField in form.formFields">
<input ng-model="formField.modelBinding" /></div>
This will result in a text box being added to my form, with ng-model="formField.modelBinding" and the textbox value = 'project.data'.
What I am trying to achieve is to set ng-model = 'project.data', in other words replace formField.modelBinding with the value of formField.modelBinding.
One approach that seemed logical was
<input ng-model = "{{formField.modelBinding}}" />
but this is obviously not going to work. I've tried to insert the HTML tags with ng-bind-html but this seems to only work with ng-bind, not ng-model.
Any suggestions?
Assuming that you are trying to bind a value to a model from a name that you have within the formField you can create a directive (aka ngModelName) to bind your model by name from this value.
Observation: My first thought was using a simple accessor like model[formField.modelBinding] which would simple bind the formField.modelBinding into a model member on scope. However, I didn't use the property accessor because it would create a property named by formField.modelBinding value and not the correct object hierarchy expected. For example, the case described on this question, project.data would create an object { 'project.data': 'my data' } but not { 'project': { data: 'my data'}} as it should.
angular.module('myApp', [])
.directive('ngModelName', ['$compile', function ($compile) {
return {
restrict: 'A',
priority: 1000,
link: function (scope, element, attrs) {
scope.$watch(attrs.ngModelName, function(ngModelName) {
// no need to bind a model
if (attrs.ngModel == ngModelName || !ngModelName) return;
element.attr('ng-model', ngModelName);
// remove ngModel if it's empty
if (ngModelName == '') {
element.removeAttr('ng-model');
}
// clean the previous event handlers,
// to rebinded on the next compile
element.unbind();
//recompile to apply ngModel, and rebind events
$compile(element)(scope);
});
}
};
}])
.controller('myController', function ($scope) {
$scope.form = {
formFields: [
{
modelBinding: 'model.project.data'
}
]
};
$scope.model = {};
});
angular.element(document).ready(function () {
angular.bootstrap(document, ['myApp']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="myController">
<div ng-repeat="formField in form.formFields">
<input ng-model-name="formField.modelBinding" placeholder="{{ formField.modelBinding }}" />
</div>
<div>
<pre>{{ model | json }}</pre>
</div>
</div>
I guess the "modelBinding" attribute has the model name of the formfield, so, in that case you should do this:
<div ng-repeat="formField in form.formFields">
<input ng-model="form.formFields[formField.modelBinding]" />
</div>
Use the modelBinding as the key to retrieve from formFields.

ng-model versus getElementById

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.

AngularJS - Directive to append input value to a list and bind to model

End goal: Associate multiple email addresses, each with a frequency setting (daily,weekly,monthly), to a notification.
I am attempting to define a directive which acts on a button element, such that, when the button is clicked, it takes the email address from the input element and the frequency from the drop-down next to the button and inserts them into a list below the input+button and binds the dynamic list to a property on the controller so it can be sent to the server when the user submits the form.
The form is all wired up - thats not the question.
I just want some help in building the directive.
Here's what i have so far:
HTML:
<section ng-controller="notificationmanagement as vm">
<input name="email" type="email"/>
<select>
<option>Daily</option>
<option>Monthly</option>
<option>Weekly</option>
</select>
<button data-cm-click type="button" class="btn btn-default"></button>
<div ng-model="vm.Subscribers"></div>
</section>
Directive:
commonModule.directive('cmClick', function () {
return function (scope, element) {
element.bind("click", function () {
// Is this the best way to get the value of the EmailAddress & Frequency?
var emailAddress = element[0].parentElement.parentElement.children[0].value;
var frequency = element[0].parentElement.parentElement.children[1].value;
var spanEmailTag = angular.element('<span>' + emailAddress + '</span>');
var spanFreqTag = angular.element('<span>' + frequency + '</span>');
angular.element(spanEmailTag).appendTo(element[0].parentElement.parentElement);
angular.element(spanFreqTag).appendTo(element[0].parentElement.parentElement);
// How to make sure each added email+frequency is available
// in the array bound to 'vm.Subscribers'
});
}
});
The structure of 'vm.Subscribers' should be something like this, in order for it be consumed by the server:
vm.Subscribers = [ {'EmailAddress':'joe#email.com', 'Frequency':'Daily'}, {'EmailAddress':'bob#email.com', 'Frequency':'Monthly'} ];
NOTE: I would ideally like to achieve this without relying on jQuery within the directive.
Any and all pointers/help/advice would be most appreciated!
If you want to encapsulate and reuse some functionality, then, by all means, use a directive.
But first, understand the MVVM (Model-View-ViewModel) paradigm and how Angular implements this.
For starters, assume that there is no View (and so, no DOM, directives, etc...) and that user inputs magically occur when something is exposed on the $scope (or, if you are using ControllerAs - as a property of the controller). Now, build you app's functionality starting from the controller. Define the data structure and the behavior with functions.
app.controller("notificationmanagement", function(){
// list of subscribers. You could also populate it from the backend.
this.subscribers = [];
// this is where the details of a new subscriber will be stored
// until subscriber is added
this.newSubscriber = {EmailAddress: null, Frequency: null};
// action to add a susbscriber
this.addSubscriber = function(){
this.subscribers.push(this.newSubscriber);
// clear the object (it's not necessary to declare all the properties)
this.newSubscriber = {};
}
});
That is, in a nutshell, all your app is doing. The controller doesn't (and shouldn't) care how this is displayed in the View. This is why DOM manipulation is frown upon, because it breaks separation of concerns.
Now, to the View. The View is mostly declarative:
<section ng-controller="notificationmanagement as vm">
<input ng-model="vm.newSubscriber.EmailAddress" type="email>
<select ng-model="vm.newSubscriber.Frequency">
<option value="Daily">Daily</option>
<option value="Monthly">Monthly</option>
<option value="Weekly">Weekly</option>
</select>
<button ng-click="vm.addSubscriber()"> Add </button>
<hr/>
<h3>All subscribers:</h3>
<div ng-repeat="s in vm.subscribers">
<span>{{s.EmailAddress}}</span>
<span>{{s.Frequency}}</span>
</div>
</section>
Notice how ng-model directives bind input data to controller's data. Notice ng-click that invokes an action on the controller. Notice ng-repeat that iterates and creates DOM elements based on that data. The View is purely driven by data, which is referred to by ViewModel.
Understand this first, then move onto directives.

ng-model not updating the value to object using templates

I am working on an MVC application using Angular. I need to open various bootstrap modal in one of the application pages. for that i just wrote a simple angular service to get the template for modal from a folder called templates and load at Run-time. Everything works fine except one thing. ng-model is not working for check box controls and DropDownList(select) items.
service to load template:
var defaultPath = "/app/services/dialog/templates/";
function _loadModalTemplate(templateName) {
var defer = $q.defer();
if (angular.isUndefined($templateCache.get(templateName))) {
return $http.get(defaultPath + templateName).then(function (data) {
$templateCache.put(templateName, data.data);
return defer.resolve();
});
} else {
return $.when($templateCache.get(templateName));
}
return defer.promise;
}
Controller
notebook.controller('createworkitemcontroller', ['$scope', '$modalInstance', 'workitemDataContext', 'common', 'options',
function ($scope, $modalInstance, workitemDataContext, common, options) {
$scope.activities = options.activities || [];
$scope.activity = $scope.activities[0];
}]);
Template HTML
<div class="col-lg-6">
<label class="text-xs">Activity</label>
<select class="form-control input-sm" data-ng-options="a.Name for a in activities" data-ng-model="activity"></select>
</div>
Data Binding is working fine but its not updating the property $scope.activity when any change is made. Same case with checkboxes as well but working with TextBox
I'm wondering if the assignment is breaking the chain of scope. Try adding your variables inside a dedicated object like $scope.model = {activity: ...} and see if it works. I ran into this when I started using multiple modals and controllers. Here's a fiddle I made a while back demonstrating the concept:
http://jsfiddle.net/6XDtN/
http://jsfiddle.net/6XDtN/1/
In the first one, the parent is oblivious because the child re-defined the variable, breaking the chain of scope. It isn't obvious it re-defined the variable, but there's no way to really re-assign a value without doing so.
In the second one, the complex type (i.e. an object), is not redefined, just a property is updated. Thus, the chain of scope is strong in this one.
Hope this helps!

Saving new models using AngularJS and $resource

I'm trying to get an understanding of AngularJS using $resource, however most of the examples I see out there don't explain how to actually create new instances of something using $resource (or how the entire setup should look).
I've posted my code at the bottom of this.
I have the following setup, where posting to '/entry/api' should create a new entry.
The entry object it self has three properties: name, description and id.
i thought that calling
$scope.save(); would do two things:
Map the input fields as POST data
make a POST request to the url defined in the $resource (in this case '/entry/api')
Some examples I've seen are manually mapping the data to the resource as such:
var entry = new Entry();
entry.name = $name; // defined in entryController
entry.description = $scope.description; // <-- defined in entryController
entry.$save()
I thought this wasn't supposed to be necessary, as these fields are defined in the html.
This solution results in:
Defining a model in the backend
Defining a model in the front end (the entryController div)
Adding the values from the from the entryController div to the JS version of the model and then finally saving it.
It might be the way AngularJS works, however I thought that the input fields in the html would automatically be mapped.
Otherwise you have at least 3 places in the code to update if you add or remove a property of your (backend) model.
How are you supposed to use AngularJS along with $resource to save new objects?
angular.module('entryManager', ['ngResource']);
function pollController($scope, $resource) {
$scope.polls = $resource('/entry/api/:id', {id: '#id'});
$scope.saveEntry = function() {
this.save();
}
}
<html ng-app="entryManager">
... <-- include angularjs, resource etc.
<div ng-controller="entryController">
<input type='text' ng-model="name"><br/>
<textarea ng-model="description" required></textarea><br/>
<button class="btn btn-primary" ng-click="saveEntry()">Save</button>
</div>
The first think you should note, that scope != model, but scope can contain model(s).
You should have some object in your scope and then save it.
So, there would be something like the following:
HTML:
<div ng-controller="entryController">
<input type='text' ng-model="poll.name"><br/>
<textarea ng-model="poll.description" required></textarea><br/>
<button class="btn btn-primary" ng-click="saveEntry()">Save</button>
</div>
JavaScript:
function pollController($scope, $resource) {
var polls = $resource('/entry/api/:id', {id: '#id'});
$scope.saveEntry = function() {
polls.save($scope.poll);
}
}
Note1: even if you do not have initialized poll object, AngularJS will automatically create new object when you start typing.
Note2: its better to wrap your form into ngForm (by adding ng-form="someformname" attribute to div with ng-controller or wrap with <form name='...'>..</form>. In this case you could check validity of form by $scope.someformname.$valid before saving.
Good example is on main page of AngularJS web site under "wiring the backend" section (btw, mine favorite).
Don't use save method over model object itself, use save method of model class, For example -
//inject User resource here
$scope.user = new User();
$scope.user.name = "etc";
User.save($scope.user,function(response){
});
previously i was using $scope.user.$save(function(response){}) it was clearing my $scope.user object

Resources