AngularJS: why empty model fields are removed from object? - javascript

In my application I have a model attached to a form which is something like that:
$scope.location = { description: "my descriptive description", address: "blah" }
Cleaning the field "description" in the form, which is bound to ng-model="location.description", removes the field from $scope.location which becomes:
$scope.location = { address: "blah" }
Now I would like it to retain the "description" field. What can I do in order to achieve
this behaviour?
Thanks for your help

One possibility is using the ng-change directive:
<input ng-model="desc" ng-change="setDescription()">
And in your controller:
$scope.setDescription = function(){
$scope.location.description = $scope.desc ? $scope.desc: "default"
}
Building off of Kozlowski's comment: http://jsfiddle.net/myMyQ/2/

Related

Angular changing dataset without $watch

I have an array of objects like this
UserList = [
{name:'user1',id:1,data:{}},
{name:'user4',id:4,data:{}},
{name:'user7',id:7,data:{}}
]
And html select like this
<select ng-model="data.selectedUser">
<option ng-repeat="item in data.items" value="{{item.id}}">{{item.name}}</option>
</select>
<p>{{data.userPhone}}</p>
Inside my controller I use
$scope.data = {};
$scope.data.selectedUser = 0;
$scope.data.items = UserListModel.items;
$scope.data.userPhone = UserListModel.items[$scope.data.selectedUser].phone;
Is there a way to update selected user phone on selectedUser change without using $watch and stuffing the "$scope.data.userPhone" inside it?
Imagine you have a data like this:
$scope.data = {};
//set the data
$scope.data= [{
id: 1,
name: "cyril",
phone: "1234567"
}, {
id: 2,
name: "josh",
phone: "1237"
}, {
id: 3,
name: "sim",
phone: "4567"
}];
//selected hold the object that is selected in the selectbox.
$scope.selected = $scope.data[0];
Your html will look like this below so now when you select the new user from the list it will be updated in the model selectedItem, the selectedItem has the phone number in it (so you dont need a watch to update phone number seperately as you doing).
<body ng-controller="MainCtrl">
<p>selected item is : {{selectedItem}}</p>
<p> name of selected item is : {{selectedItem.name}} </p>
<select ng-model="selectedItem" ng-options="item.name for item in items track by item.id"></select>
</body>
working example here
One possibility would be to have
$scope.data.userPhone = function () {
return UserListModel.items[$scope.data.selectedUser].phone;
}
This would mean though that you'd have to update any bindings to use data.userPhone() instead.
This might be worse than using a watch though, as the function would get called during every digest.
Without knowing how selectedUser gets updated it's difficult to suggest a best way as, with most things, it depends.

Angularjs - how to see if a value is in array?

I have a model similar to this one:
model = [
{
name: 'Chris',
age: '29',
approvedBy: ['id1', 'id2', 'id3']
},
{
// repeat...
}
]
in my view I have a ng-repeat for the model and there is a button to approve or disapprove the user.
Basically other users can approve or disapprove, if previously approved, the user. Quite similar to like or unlike a FB post.
<div ng-repeat='user in model'>
<p>Name: {{user.name}} - Age: {{user.age}}</p>
<p>Approved by {{user.approvedBy.length}} users.</p>
<button>Approve {{user.name}}</button>
</div>
It works fine, so I can approve and disapprove the user but I can not figure out how can I change the text of Approve.
I wish it to be Disapprove if has already been approved by the user in the session.
I'm looking for something like indexOf to create an inline if... or a filter...
It should look like:
<button>(user.approvedBy.indexOf(currentUserId)) ? 'Disapprove' : 'Approve'</button>
How can I create something like that in Angularjs?
A simple approach would be:
Add a custom filter to the controller and then use it in the view:
In controller:
$scope.model = [...];
$scope.approveTextFilter = function (user) {
return user.approvedBy.indexOf(currentUserId) !== -1 ? 'Disapprove' : 'Approve';
};
In view:
<div ng-repeat='user in model'>
...
...
<button ng-bind="user | filter:approveTextFilter"></button>
</div>

AngularJS: How to create a model which holds an array for a dynamic list of input fields?

I have quite an interesting question (I hope) for all you AngularJS gurus out there. I am looking to create a dynamic list of form input fields based on a SELECT dropdown. As an example, we have a number of categories with each category having a set of specifications which are unique to that category. To help with the explanation we have the following:
Firstly, in the controller we start by initializing the models.
$scope.category = {};
$scope.category.specs = [];
Next we ready the data to be used in the form (actually retrieved from the server via $http). We also initialize a variable to the first element in the categories array.
$scope.categories = [
{ "id": "1", "name": "mobile", specs: [
{ "id": "1", "label": "Operating System" },
{ "id": "2", "label": "Camera type" } ] },
{ "id": "2", "name": "laptop", specs: [
{ "id": "1", "label": "Operating System" },
{ "id": "2", "label": "Graphics Card" } ] }
};
$scope.selectedCategory = $scope.categories[0];
In the form, we have a dropdown which when selected loads the appropriate input fields specific to that category. We use the ngRepeat directive to accomplish this. This is a dynamic list of fields based on $scope.categories.specs. (please note the ???)
<select ng-model="selectedCategory" ng-options="category.name for category in categories"></select>
<div ng-repeat="spec in selectedCategory.specs">
<label>{{spec.label}}</label>
<input type="text" ng-model="???">
</div>
Ultimately, when the user clicks the submit button, we would like to extract the category he/she has selected and then package it together with the specifications they have filled in. The post request should contain something like the following for instance (of course, I only included one spec item, but in reality there would be many):
{ "id": "1", specs [ { "id": "2", "details": "RADEON HD 8970M" } ] }
Unfortunately I am not really sure how to accomplish this. I need to somehow create an array for the spec model, and then ensure that both the ID and user entered data are appropriately extracted... what goes in the ??? and what do we do after? Any help would be much appreciated.
this is how I do it. I make a form, validate it with angular, and then when its valid I submit it with a function.
<form name="signup_form" novalidate ng-submit="signupForm()"></form>
$scope.signupForm = function() {
var data = $scope.signup;
$http({
method : 'POST',
url : 'http://yoursite.com/mail.php',
data : $.param(data), // pass in data as strings
headers : { 'Content-Type': 'application/x-www-form-urlencoded' } // set the headers so angular passing info as form data (not request payload)
})
.success(function(data) {
});
}
also if you want to look at another form validation system for angular check out http://nimbly.github.io/angular-formly/#!/ It may help you solve your current form system.
In the controller, initialize $scope.specDetails as follows:
$scope.specDetails = {};
angular.forEach($scope.categories, function (category, index1) {
$scope.specDetails[category.id] = {};
angular.forEach(category.specs, function (spec, index2) {
$scope.specDetails[category.id][spec.id] = '';
});
});
In the html, replace "???" with specDetails[selectedCategory.id][spec.id]

Angular.js data accessor

I'm trying to learn Angular, and I'm stuck on the following.
I have a PHP background, mostly use Laravel, and in Laravel you can use accessors in your models. So if you have a model User, which has a firstname and lastname. You can create an accessor for fullname which will return both the firstname and lastname:
public function getFullNameAttribute()
{
return $this->firstname . ' ' . $this->lastname;
}
Now, I would like to do the same thing in Angular.
I have a UserController with a user:
function UserController($scope) {
$scope.users = [
{ firstname: 'John', lastname: 'Doe' }
];
}
In the "view" I want to loop over the users with ng-repeat, but instead of doing
{{ user.firstname }} {{ user.lastname }}
I would like to be able to just use
{{ user.fullname }}
Maybe it's called differently in Angular or something, but I just can't seem to find it...
Thanks for the help!
Angular does not yet natively provide model abstractions (beside simplistic $resource service which deals mostly with RESTfull communication layer).
But that doesn't mean you can't write and re-use your own custom model accessors.
Option 1 - Use custom filter
PLUNKER
app.filter("fullName", function () {
return function (user){
return user && [user.firstName, user.lastName].join(' ');
}
});
app.controller("MainCtrl", [
"$scope",
function($scope) {
$scope.user = {
firstName: 'John',
lastName: 'Doe'
};
}
]);
User fullName: {{user | fullName}}
 
Option 2 - Use custom $resource instance accessors:
PLUNKER
app.factory('UserService', function ($resource){
// User is an instance of $resource service which
// in this example uses mock server endpoint
var User = $resource('user.json');
// Custom User accessor method
User.prototype.fullName = function(){
return [this.firstName, this.lastName].join(' ');
}
return User;
});
app.controller("MainCtrl", [
"$scope",
"UserService",
function($scope, UserService) {
$scope.user = UserService.get()
}
]);
User fullName: {{user.fullName()}}
In addition to Stewie's very sophisticated techniques, you can also use ng-init.
<div ng-repeat="user in users"
ng-init="user.fullname = user.firstname + ' ' + user.lastname">
{{user.fullname}}
</div>
http://plnkr.co/zU6vM5f8pI3Veh7jr1R9
angular does not have a model framework, here your scope is the model and any javascript object (plain old javascript object) attached to it. You might be interested in this article which talks about implementing an OO model layer on angular js. https://medium.com/opinionated-angularjs/2e6a067c73bc. Like others have said, implementing filters or directives might be the easiest and is also reusable.

AngularJS - Change the scope of a directive

I'm trying to set up an Angular Directive so that I can re-use a piece of HTML. I have managed to achieve this, but the problem I am facing is when I want to pass some value into that templated HTML, from the containing HTML.
For simplicity's sake, I will use an example of a customer that has multiple addresses (in this context, the customer is an object and each address instance is another object attached to the customer).
Example of the data:
var customer = {
forename: "John",
surname: "Smith",
address1: {
street: "123 Street",
town: "Georgeville",
},
address2: {
street: "67 Maple Grove",
town: "SomeTown"
}
};
Here is an example of my directive setup:
var module = angular.module(...);
module.directive("address", function () {
return {
restrict: 'A',
templateUrl: '/AddressView.html'
};
});
And my attempted usage:
<div ng-model="customer">
<div address></div>
<div address></div>
</div>
And this is what I would like to do to be able to pass the customer's addresses across to the templated HTML:
<div ng-model="customer">
<div address ng-model="customer.address1"></div>
<div address ng-model="customer.address2"></div>
</div>
I may have misunderstood the purpose of directives, or this may not be possible, but if anyone has any suggestions they would be greatly appreciated.
Let me know if you need me to add any further information.
Edit
Here is a Plunker that I have set up to try to illustrate what I am trying to achieve.
you need to isolate the scope for your directive so that things don't get confused for angular.
And your object is better structure this way:
var customer = {
forename: "John",
surname: "Smith",
addresses: [
address1: {
street: "123 Street",
town: "Georgeville",
},
address2: {
street: "67 Maple Grove",
town: "SomeTown"
}
]
};
this way you can do this:
<div class="customer">
<div address ng-repeat="address in customer.addresses">
{{address.town}} {{address.street}}
</div>
</div>
I don't know why you'r using ng-model here?!
This is an example, but if you provide a plunker with an example code helping you will be easier.
Update:
First your ng-controller was in the wrong place you need to move it up so the address directive could be in the scope of the controller so it could access the address object.
Second you have 2 undefined variables: street and town.
And you need to isolate the scope so each directive don't mess with the variables of the other one.
Here's a working plunker.
Hope this help.

Categories