How can I use Variable expressions in AngularJS - angularjs

In PHP I can use a double dollar sign to use a variable to represent another variable. If like to do the same in AngularJS expressions.
For example if I had an object with property1, property2 etc each with different values I would reference them directly as {{$scope.property1}}
What I would like to do is something like string propertyToDisplay = 'property1' and then use {{$scope.propertyToDisplay}} show the value stored in property1.
Does AngularJS support this?

This appears to be an XY problem. In general, the $scope variable shouldn't be appearing in your views at all, let alone that you seem to be trying to go outside the boundaries of what should be put in the markup for an AngularJS view.
In general, the parts of your controller that should appear in your view are (1) properties of the scope (or properties of properties, etc.) (2) functions that are part of the scope (with scope properties passed in as arguments). For anything involving dynamic functionality, you should generally be using the latter. So you could have your controller defined like this:
angular.module('myModule', [])
.controller('myController', ['$scope', function ($scope) {
$scope.properties = {
property1: "Hey!",
property2: "Hi"
};
$scope.selectedProperty = '';
$scope.getProperty = function (propertyName) {
return $scope.properties[propertyName];
};
}]);
And then your view could have something like this:
<select ng-model="selectedProperty">
<option value="property1">Property 1</option>
<option value="property2">Property 2</option>
</select>
<p>The value of the property you selected is: {{getProperty(selectedProperty)}}</p>

You can access variable object attribute using square brackets:
attrname ='attribute_name';
obj[attrname] = something;

How about using another object to store the visibility of each object property (DEMO).
$scope.visible = {};
$scope.person = {
first_name: 'John',
last_name: 'Rambo',
city: 'Hollywood'
};
Create a list with checkboxes for each property:
<div ng-repeat="(key, value) in person">
<input id="{{key}}Check" type="checkbox" ng-model="visible[key]">
<label for="{{key}}Check">{{key}}</label>
</div>
And check the visibility of each property in the form:
<input type="text" ng-model="person.first_name" ng-show="visible['first_name']">
If you want the form to be dynamic too (only input fields in this case) you can of course put this in a ng-repeat too:
<input ng-repeat="(key, value) in person" type="text" ng-model="person[key]" ng-show="visible[key]">
Hope this is at least close to what you want.

Related

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.

i need an alternative solution for ng-change

i tried calling the values using ng-change between two different ng-models. it works.
but the data is parsed to the other ng-model only if the data is changed, is there any alternate solution where i can have the data in both ng-models before changing data
I tried something like this
HTML
<input ng-model="customer.name" ng-change='tripsheet.customer_name=customer.name;'>
<input ng-model="tripsheet.customer_name" type="text" class="form-control input-lg" placeholder="Customer Name">
JS
$scope.customer = {
name:$scope.customers[$scope.whichItem].name,
address:$scope.customers[$scope.whichItem].address,
phone:$scope.customers[$scope.whichItem].phone
}
i want the routeParams data in both the above ng-models.
With something like this in your controller?
$scope.customer = {
name: "Tom"
};
$scope.tripsheet = {
customer_name: $scope.customer.name
}
Plunker
#mainguy's answer is a very good approach.
Or alternatively you can go with $watch instead of ng-change.
initialize a watcher for the model object
$scope.$watch('customer.name', customerNameChanged);
and define the function
function customerNameChanged() {
if(!$scope.tripsheet)
$scope.tripsheet = {};
$scope.tripsheet.customer_name = $scope.customer.name
}
ng-change triggers only when there is a user interaction and the data is changed.
$watch is triggered when the model object is changed programmatically and also when they’re being defined the first time

why select items are not being populated

Here is my html code
<select id="userGroups" name="userGroups" ng-model="userGroups" class="form-control">
<option value="{{grp.groupId}}" ng-repeat="grp in groups">{{grp.groupName}}</option>
</select>
here is my controller
function MyController($scope, MYAPI) {
$scope.groups = MYAPI.GroupList.get();
}
Why options are not being popuplated?
Ok I have changed my controller to resolve the GroupList before populating the view, but its still not showing
MYApp.controller('CreateUserController', ['$scope', 'groupList', function($scope, groupList) {
$scope.groups = groupList;
debugger; //here I can see groups has objects which I need to display
}]);
but still dropdown is not loading...
As Martin said you need to use ng-options.
This is how it should look like:
<select id="userGroups"
name="userGroups"
ng-model="userGroups"
ng-options="grp.groupId as grp.groupName for grp in groups"
class="form-control">
</select>
Accorting to the AngularJS documentation of select you have to use ng-options. This use case is not supported by ng-repeat:
ngOptions provides an iterator facility for the <option> element which should be used instead of ngRepeat when you want the select model to be bound to a non-string value. This is because an option element can only be bound to string values at present.

Sharing data between directives using attributes instead of services

I wanted to make a directive that would essentially act like a specialized input field. After some logic & user input, the 'value' attribute would be populated with a string of comma separated timeslots (hh:mm).
<time-slot value=""></time-slot>
becomes
<time-slot value="01:00,02:00,03:00"></time-slot>
I'd like to provide the flexibility for anyone to place a scope reference in the 'value' attribute tag -- whenever the attribute value is updated, so is the scope reference. (In the code below, myModel.times would be in the MyController scope).
<div ng-controller="MyController">
<time-slot value="{{ myModel.times }}"></time-slot>
</div>
I have had no problems accessing the 'value' attribute in the directive. However, I have not achieved two-way binding -- myModel.times never captures the changed value, even though the contents of the attribute have been changed when I inspect the element during runtime. I am using $attrs.$set to alter the value attribute.
I'm not sure if I'm missing something conceptually, or just missing some extra syntax. To keep this directive modular and shareable, I don't want to use a service to share data between the controller and directive, nor do I want to use a cascading scope. I think it would be optimal if the value attribute can simply be referenced by a scope variable and used as desired, much like a simple input tag:
<input ng-model="model.someText"></input>
An example with two-way data binding: See plunkr.
angular.module('myApp', [])
.directive('timeSlots', function() {
return {
scope: { value: '=' },
link: function($scope, $elem, $attrs) {
// you can access $scope.value here (after it has been interpolated)
}
};
})
.controller('MainCtrl', ['$scope', function($scope) {
$scope.value = 42;
}]);
In HTML:
<div ng-controller="MainCtrl">
<time-slots value="value"></time-slots>
</div>

angular.js Table Filtering

I was trying to create a table with a filter at the top of each column.
I am unable to figure out why the Any filter is working but not the individual colum filters.
I have the code # http://plnkr.co/edit/6ghH02
I have also tried to compile the element after setting the ng-model but it did not work.
Can anyone please help?
You can do this if you get rid of the directive and use the ng-change attribute of the input[text] like so:
function TestController ($scope) {
$scope.valueChanged = function (name, value) {
if (!$scope.search) {
$scope.search = {};
}
$scope.search[name] = value;
}
};
<body ng-controller="TestController">
<th ng-repeat="sortName in columnNames">
<input type=text ng-model="value" ng-change="valueChanged(sortName, value)">
</th>
The short version of the answer as to why your original code doesn't work is that ngRepeat creates a new scope and the value in ng-model must be an assignable variable.
As #Ryan pointed out, ng-repeat's nested scopes are the trick. An alternative to writing a controller method is to use the $parent property of the child scope to access the search property in the parent scope:
<th ng-repeat="sortName in columnNames">
<input type=text ng-model="$parent.search[sortName]"></th>
As in #Ryan's solution, a directive is not needed.
However, the additional search properties needed to be created/initialized:
<div ng-init="search.name = ''; search.phone = ''"></div>
http://plnkr.co/edit/wT34vh?p=preview

Resources