AngularJS ng-bind to specific array element - angularjs

I have elements in my page that's being repeated as rendered by ASP.Net MVC. (I need to do this for SEO purposes.)
Within these elements, I would like to have a text field that's bound by AngularJS.
Is there a way to do this?
In the code below, for example, the 2nd and 3rd text fields don't even allow me to type anything. Is it because I have not properly initialized the array or is there something wrong with the syntax?
<input type="text" ng-model="sometext" />
<input type="text" ng-model="sometextArray1[1]" />
<input type="text" ng-model="sometextArray2[1].value" />
<h1>Hello #1 {{ sometext }}</h1>
<h1>Hello #2 {{ sometextArray1[1] }}</h1>
<h1>Hello #3 {{ sometextArray2[1].value }}</h1>

If you are trying to let angular implicitly instantiate the array for you, I don't think arrays are assignable. If you are using a controller to instantiate your array this should work. I have created a fiddle.
JsFiddle
Html:
<div ng-app="mod">
<section ng-controller="Ctrl">
<input type="text" ng-model="someArray[0].value" ng-change="logValue(0)" />
<input type="text" ng-model="someArray[1].value" ng-change="logValue(1)"/>
<input type="text" ng-model="someArray[2].value" ng-change="logValue(2)"/>
<input type="text" ng-model="someArray[3].value" ng-change="logValue(3)"/>
</section>
</div>
Code:
(function () {
var app = angular.module('mod', []);
app.controller('Ctrl', ['$scope', '$log', function ($scope, $log) {
$scope.someArray = [
{value: "value 0"},
{value: "value 1"},
{value: "value 2"},
{value: "value 3"}
];
$scope.logValue = function (index) {
$log.info($scope.someArray[index]);
}
}]);
})();

You can add a directive to your list and another for each item using isolate scope.
Here's a plunker
you will get all the content of your items by transcluding them into a directive template
transclude: true,
//
<div ng-transclude></div>
You can add to your isolate scope per item from the attribute like you just need a way to index each scope like scope="scope1" | scope="scope2"
<list-item name="First Item" scope="scope1" func="func1(scope)">
In your directive you use operators like - # String, = Model, & Function
// Directive
scope: {
'name': '#',
'iscope': '=scope',
'func': '&'
}
Then in the container controller you can create the a sort of indexed array of item scopes like you would with an ng-repeat.
$scope.addItem = function(scope) {
$scope.items.push(scope);
};

Related

Angularjs calculate based on formula given

Im new in Angularjs
and I would like to do something like the following in directive
<!--get numb1&numb2 from user input-->
<div>
<input ng-model="numb1" type=number/>
</div>
<div>
<input ng-model="numb2" type=number/>
</div>
<!--result display on the following input box, not allow to edit-->
<div>
<input ng-model="result" formula="some formula here, can be anything" readonly/>
</div>
numb1 & numb2 can be change anytime, use $watch instead on ngChange.
Can anyone guide me on this?
You can achieve your requirement with the following code snippet.
The HTML
<div ng-app="demo">
<div ng-controller="DefaultController as vm">
<inputs formula="vm.formula"></inputs>
</div>
</div>
<script type="text/template" id="inputsTemplate">
<div>
<input type="number" ng-model="vm.a"/>
</div>
<div>
<input type="number" ng-model="vm.b"/>
</div>
<div>
<input type="number" ng-model="vm.result" readonly/>
</div>
</script>
The AngularJS code
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.controller('InputsController', InputsController)
.directive('inputs', inputs);
function DefaultController() {
var vm = this;
vm.formula = 'a + b';
}
function inputs() {
var directive = {
restrict: 'E',
scope: {
formula: '='
},
template: function () {
return angular.element(document.querySelector('#inputsTemplate')).html();
},
controller: InputsController,
controllerAs: 'vm',
bindToController: true
};
return directive;
}
InputsController.$inject = ['$scope', '$parse'];
function InputsController($scope, $parse) {
var vm = this;
var expressionFunc = $parse(vm.formula);
$scope.$watchGroup(['vm.a', 'vm.b'], function (newValue, oldValue, scope) {
if (angular.isDefined(vm.a) && angular.isDefined(vm.b)) {
var result = expressionFunc(vm);
vm.result = result;
}
});
}
The jsfiddle link here
The coding style is followed from angular-styleguide by John Papa and from Pro AngularJS by Adam Freeman
Explanation:
There is a main controller which is having a formula set on its scope such as a + b and which is passed to a directive called inputs through an attribute called formula, this an element directive meaning that it is restricted to be applied only as an individual html element.
The inputs directive gets its template from a script tag having html content
by using the template property of the directive definition. The inputs directive has its own controller bound using the controller aliasing syntax.
The inputs directive has three objects on its controller scope they're a, b and result and the magic happens in the InputsController using the $parse service which returns a function when an expression is parsed which can indeed be evaluated on an object to execute that expression and get the result i.e.
var expressionFunc = $parse(vm.formula);
var result = expressionFunc(vm);
vm.result = result;
Also as we need to watch both a and b objects on the scope, we can use the $watchGroup function on the $scope object to handle the change events and update the view accordingly.
Note: we are only parsing once var expressionFunc = $parse(vm.formula); and executing the expression many times var result = expressionFunc(vm); this is an important factor and should be considered for quality of the code and for performance.
You are in the same scope, so, you can use ng-value in order to set a new value given numb1 and numb2. For example, you formula is numb1 + numb2, just try something like this:
<input ng-model="result" ng-value="{{numb1 + numb2}}" readonly/>

Use an Angular directive to generate html from an array

I'm trying to use an Angular directive to create a form where the user can specify the number of children and, for each child, an edit box appears allowing the childs date of birth to be entered.
Here's my HTML:
<div ng-app>
<div ng-controller="KidCtrl">
<form>
How many children:<input ng-model="numChildren" ng-change="onChange()"/><br/>
<ul>
<li ng-repeat="child in children">
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Here's the JS:
var app=angular.module('myApp', []);
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{dob: "1/1/90"}, {dob: "1/1/95"}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function() {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
};
});
And here's a jsFiddle
The problem is that it's just not working.
If I enter 1 in the numChildren field then it shows 1 bullet point for the list element but it doesn't show any of the HTML.
If I enter 2 in the numChildren field then it doesn't show any list elements.
Can anyone explain what I'm doing wrong?
Many thanks ...
Your main issue is that the directive childDOB is never rendered. Even though your controller works because 1.2.x version of angular has global controller discover on. It will look for any public constructors in the global scope to match the controller name in the ng-controller directive. It does not happen for directive. So the absence of ng-app="appname" there is no way the directive gets rendered. So add the appname ng-app="myApp" and see it working. It is also a good practice not to pollute global scope and register controller properly with controller() constructor. (Global look up has anyways been deprecated as of 1.3.x and can only be turned off at global level.)
You would also need to add track by in ng-repeat due to the repeater that can occur due to increasing the length of the array based on textbox value. It can result in multiple array values to be undefined resulting in duplicate. SO:-
ng-repeat="child in children track by $index"
Fiddle
Html
<div ng-app="myApp">
<div ng-controller="KidCtrl">
<form>How many children:
<input ng-model="numChildren" ng-change="onChange()" />
<br/>
<ul>
<li ng-repeat="child in children track by $index">{{$index}}
<child-dob></child-dob>
</li>
</ul>
</form>
</div>
</div>
Script
(function () {
var app = angular.module('myApp', []);
app.controller('KidCtrl', KidCtrl);
KidCtrl.$inject = ['$scope'];
function KidCtrl($scope) {
$scope.numChildren = 2
$scope.children = [{
dob: "1/1/1990"
}, {
dob: "1/1/1995"
}];
$scope.onChange = function () {
$scope.children.length = $scope.numChildren;
}
}
app.directive('childDob', function () {
return {
restrict: 'E',
template: 'Child {{$index+1}} - date of birth: <input ng-model="child.dob" required/>'
}
});
})();

How to dynamically add ng-repeat to an element inside an angular directive?

I want to add 'ng-repeat="n in counter"' to the 'form' tag inside my directive. How do I do this?
I tried accessing the element via compile but tElement.find('form') does not work.
See : http://jsfiddle.net/fea40v2c/1/
I tried all these variations:
console.log(tElement.find('form')); // fails
console.log(tElement[0].querySelector('form')); // null
console.log(document.querySelector('form')); // fails
Do you really need the add button to be defined by the directive's user? Because if you don't you can do this.
<script id="repeatableForm.html" type="text/ng-template">
<input type="button" value="add" ng-click="add()">
<div ng-repeat="c in counter">
<div ng-transclude></div>
</div>
</script>
Update
After a little work I got something that allows the user to provide their own markup for the add button. It a bit more complicated and involves a nested directive. A few points that are good to know:
The repeatableForm directive has no isolated scope. It modifies the host scope by adding/overwriting the repeatableForm property. This means multiple such directives cannot execute in the same host scope.
The repeatableForm publishes its controller in its host scope as the repeatableForm property. This is better than publishing the controller's methods directly in the scope because it namespaces those methods and leaves the host scope cleaner.
The view
<repeatable-form>
<input type="button" value="add" ng-click="repeatableForm.add()"/>
<form action="">
First Name: <input name="fname" type="text" />
Last Name: <input name="lname" type="text" />
<input type="checkbox" name="food" value="Steak"/> Steak
<input type="checkbox" name="food" value="Egg"/> Egg
<input type="button" value="remove" ng-click="repeatableForm.remove($index)" />
</form>
</repeatable-form>
The directives
app.directive('repeatableForm', function () {
return {
templateUrl:'repeatableForm.html',
restrict: 'E',
transclude: true,
controller: function () {
var repeatableForm = this
repeatableForm.add = function () {
repeatableForm.forms.push(repeatableForm.forms.length + 1);
};
repeatableForm.remove = function (index) {
repeatableForm.forms.splice(index, 1);
};
repeatableForm.forms = [1, 2, 3];
},
controllerAs: 'repeatableForm',
};
});
app.directive('form', function () {
return {
templateUrl: 'repeatedForm.html',
restrict: 'E',
transclude: true,
};
})
The templates
<script id="repeatableForm.html" type="text/ng-template">
<div ng-transclude></div>
</script>
<script id="repeatedForm.html" type="text/ng-template">
<div ng-repeat="form in repeatableForm.forms"><div ng-transclude></div></div>
</script>
Check this demo.

Set angularjs input directive name as a variable

I'm trying to set an input like
<form name="myForm">
<input name="{{ name }}"/>
</form>
It works in the dom. I see
<input name="whatever_name_is_set_to"/>
However in my ngForm I have
$scope.myForm: {
{{ name }}: { }
}
Doh'
Why am I doing this? I'm trying to create a directive so that I can build my forms programmatically. Then I can do something like
<div my-field
name="credits"
field="course.credits"
field-options="courseOptions.credits"
title="Credits"
></div>
Plunker
Updated 2017-03-23:
For AngularJS >= 1.3 interpolation is now supported in input names.
Original answer from 2014-06-05 (correct for AngularJS <= 1.2):
I just answered a similar question yesterday about dynamically named form elements. In short, no, you can't use interpolation to dynamically name your form fields - the interpolation string will end up in the field name as you have seen.
In your case you're probably going to need to look into dynamically compiling the input HTML inside your myField directive.
Here is a simplified example using $compile to dynamically generate form elements: http://jsfiddle.net/Sly_cardinal/XKYJ3/
HTML:
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<div my-field
name="courseName"
field="course.courseName"
title="Course Name"></div>
<div my-field
name="credits"
field="course.credits"
title="Credits"></div>
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits.$dirty: {{myForm.credits.$dirty}}</pre>
</form>
</div>
JavaScript:
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: 100,
courseName: 'Programming 201'
};
}])
.directive('myField', ['$compile', '$parse', function($compile, $parse){
// In a real project you'd probably want to use '$templateCache' instead
// of having strings in your code.
var tmpl = $compile('<label>{{title}}</label>');
return {
scope: true,
link: function(scope, element, attr){
scope.title = attr.title;
var newEl = angular.element('<input type="text"/>');
newEl.attr('ng-model', attr.field);
newEl.attr('name', attr.name);
tmpl(scope, function(fieldEl, scope){
$compile(newEl[0].outerHTML)(scope, function(el, scope){
fieldEl.append(el);
element.append(fieldEl);
});
});
}
}
}]);
A note on this example:
This is a very specific situation - generating dynamic form elements - that requires the use of $compile. This is not the "go to" solution when working with Angular inputs and forms - Angular will handle all the common situations with directives, data-binding and everything else the framework provides. Plus, as Marc Kline's comment shows, it looks like at some point Angular will handle dynamic form management itself at some point in the future.
If you were to continue down the path using $compile to generate these form elements then you'd probably want to use the $templateCache to manage your templates so you're not trying to manage template strings inside your directive.
Old question, but in case someone is looking for a way to do what question asked, you can create a directive that will dynamically create the name of the element after $compile'ing it.
An updated version of the answer that #Sly_cardinal posted is here: http://jsfiddle.net/XKYJ3/1/
HTML
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<label for="{{ course.courseName.name }}" ng-bind="course.courseName.title"></label>
<input id="{{ course.courseName.name }}" dynamic-input-name="course.courseName.name" ng-model="course.courseName.value" type="text" required />
<br />
<label for="{{ course.credits.name }}" ng-bind="course.credits.title"></label>
<input id="{{ course.credits.name }}" dynamic-input-name="course.credits.name" ng-model="course.credits.value" type="number" required />
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits_field.$dirty: {{ myForm.credits_field.$dirty }}</pre>
</form>
</div>
Javascript
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: {
title: 'Credits',
value: 100,
name: 'credits_field'
},
courseName: {
title: 'Course name',
value: 'Programming 201',
name: 'course_name_field'
}
};
}])
.directive('dynamicInputName', ['$compile', '$parse', function($compile, $parse){
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-input-name'))(scope);
elem.removeAttr('dynamic-input-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
}]);

Angular way to collect values from several inputs

I have following trouble. I have several rows with
dynamically generated inputs in AngularJS view. I'm searching
elegant way to get array from this generated inputs.
This is me html:
<div ng-app>
<div ng-controller="TestCtrl">
<input type="button" value="+" ng-click="addNewRow();"/>
<div ng-repeat="item in items"><input type="text" name="key" ng-value="{item.name}"/> : <input type="text" ng-value="{item.value}"/>
<input type="button" value="x" ng-click="removeItem($index);"/>
</div>
<input type="button" value="Test" ng-click="showItems();"/>
</div>
</div>
and this is my javascript code:
function TestCtrl($scope) {
$scope.items = [
{name: "", value: ""}
];
$scope.addNewRow = function () {
$scope.items.push({
name: "",
value: ""
});
};
$scope.removeItem = function (index) {
$scope.items.splice(index,1);
};
$scope.showItems = function() {
alert($scope.items.toSource());
}
};
alert($scope.items.toSource()); will work correct only under Firefox and as you can
see array is empty. I'm searching a way to update array or other angular way
method.
document.querySelector("input[attr]") or jQuery similar is not good idea I think.
Here is working jsFiddle:
http://jsfiddle.net/zono/RCW2k/21/
I would appreciate any advice and ideas.
Best regards.
Use ngModel:
The ngModel directive binds an input,select, textarea (or custom form
control) to a property on the scope using NgModelController, which is
created and exposed by this directive.
Your view should look like:
<div ng-repeat="item in items">
<input type="text" ng-model="item.name"/> :
<input type="text" ng-model="item.value"/>
<input type="button" value="x" ng-click="removeItem($index);"/>
</div>
(As for the use of toSource() in your code, it is not part of any standard - Gecko-only)
Working example: http://jsfiddle.net/rgF37/

Resources