I'm writing an angular.js directive that is a reusable input component for an array of objects.
Since it is impossible to use primitive values in ng-repeat (see: What is the angularjs way to databind many inputs?), I have to pass an array of objects to the component:
In the controller I initialize:
$scope.theSimpsons = [{ value: 'Bart' }, { value: 'Lisa' }];
And then use it in the HTML file:
<div ng-app="App">
<div ng-controller="AppCtrl">
<multi-input items="theSimpsons" />
</div>
</div>
The directive itself is implemented like this:
directive('multiInput', function () {
return {
restrict: 'E',
scope: {
items: '=items'
},
template:
'<div>' +
'<div ng-repeat="item in items">' +
'<input type="text" ng-model="item.value">' +
'<a ng-click="items.splice($index, 1)">remove</a>' +
'</div>' +
'<a ng-click="items.push({value:\'\'})">add</a>' +
'</div>'
};
});
This is all working good.
My question: what if the objects don't have a value?
This directive codes the name of the property (value) hard. But what, if I want to have an array like this: [{ name: 'Bart' }, { name: 'Lisa' }].
Is it possible to pass the name of the object, e.g. like
<multi-input items="names" property="name"></multi-input>
and use it somehow in the directive to access the name property?
Here is the JSFiddle http://jsfiddle.net/napU6/5/ I have created to show this directive.
Use another attribute to pass the name of the property
<multi-input items="myItems" name="value"></multi-input>
Directive
app.directive('multiInput', function(){
return {
restrict: 'E',
replace: true,
scope: {
items:'=',
name: '#'
},
template:'<div>'
+ '<div ng-repeat="item in items">'
+ '<input type="text" ng-model="item[name]">'
+ '<a ng-click="items.splice($index, 1)">remove</a>'
+ '</div>'
+ '<a ng-click="items.push({})">add</a>'
+ '</div>'
}
});
Demo: Plunker
Related
How can one utilize a string contained within a variable as a template?
For example:
Controller
$scope.items = [
{
name: "Bruce Wayne"
},
{
name: "Lucius Fox"
}
];
$scope.template = "<input ng-model='item.name' type='text'>";
View
<div ng-repeat="item in items">
<div ng-bind-html="<!-- The `template` variable defined in the controller. -->">
</div>
</div>
I've tried $sce.trustAsHtml, which only doesn't connect with the actual scope when using things like ng-model='item.name'. Reading through the $sce docs I don't think that it provides anything that can trust a string as a template.
A fiddle for you to play with.
to actually bind data from ng repeat to input need to compile the html. for that this directive can be used
app.directive('dynamic', function($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(attrs.dynamic, function(html) {
element[0].innerHTML = html;
$compile(element.contents())(scope);
});
}
};
});
<div ng-repeat="item in items">
<div dynamic="template">
</div>
</div>
$scope.items = [
{
name: "Bruce Wayne"
},
{
name: "Lucius Fox"
}
];
$scope.template = "<input ng-bind='item.name' type='text'>";
Demo
I've been struggling and Googling everywhere around and I can't imagine why this directive doesn't update my controller $scope value:
Directive:
app.directive('ingFormField', function () {
return {
restrict: "E",
scope: {
value: "=",
fieldName: "#",
fieldLabel: "#"
},
div class="form-group">'+
' <label for="{{fieldName}}" class="control-label">{{fieldLabel}}:</label>'+
' <input ng-model="value" class="form-control" type="text" name="{{fieldName}}" id="{{fieldName}}" />' +
'</div>'
};
});
used in HTML:
<ing-form-field field-name="Order" field-label="Order" ng-model="lateral.Order"></ing-form-field>
And my object "lateral" from my controller:
$scope.lateral = {Order: "01", Name: "Person"}
I've tried some functions from StackOverflow answers about using a link function to update values in my controller $scope with the same output: values from $scope to the directive are working but any change on the directive's input field don't update $scope object "lateral"
This may be has an alternative aproach by using ngModel parameter directly and pass it through your template. Using ngModel will help you to keep consistence of naming things on your template.
For example, if you use your directive template using the ngModel directive, you can call it from the directive scope and use it do reflect the model to your parent controller.
The following code implement this solution, be aware that this is one of the solution, you may find different ways to do that.
angular
.module('myApp', [])
.directive('ingFormField', function() {
return {
restrict: "E",
scope: {
ngModel: "=",
fieldName: "#",
fieldLabel: "#"
},
template: '<div class="form-group">' +
' <label for="{{fieldName}}" class="control-label">{{fieldLabel}}:</label>' +
' <input ng-model="ngModel" class="form-control" type="text" name="{{fieldName}}" id="{{fieldName}}" />' +
'</div>'
};
})
.controller('myController', function($scope) {
$scope.lateral = {
Order: "01",
Name: "Person"
}
});
angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div ng-controller="myController">
<ing-form-field field-name="Order" field-label="Order" ng-model="lateral.Order">
</ing-form-field>
<pre>{{ lateral | json }}</pre>
</div>
Please see below code, you're very close. I think you may have been trying to set/pass the value using ngModel instead of the scoped property you designated for it, which was value. That's if I'm understanding correctly. Please let me know if this helps, if not I can always update.
function exampleController($scope) {
$scope.lateral = {
Order: "01",
Name: "Person"
};
}
function ingFormField() {
return {
restrict: "E",
scope: {
value: "=",
fieldName: "#",
fieldLabel: "#"
},
template: '<div class="form-group"> ' +
' <label for="{{fieldName}}" class="control-label">{{fieldLabel}}:</label>' +
' <input ng-model="value" class="form-control" type="text" name="{{fieldName}}" id="{{fieldName}}" />' +
'</div>'
};
}
angular
.module('example', [])
.controller('exampleController', exampleController)
.directive('ingFormField', ingFormField);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div class="container-fluid" ng-app="example">
<div class="container" ng-controller="exampleController">
<ing-form-field field-name="Order" field-label="Order" value="lateral.Order"></ing-form-field>
</div>
</div>
I trying to make a directive which accepting an attribute and hook it to the isolated scope, but the attribute value is not showing.
angular.module('app', [])
.controller('torrentController', [function() {
this.recommended = ['...'],
this.otherArray = ['...']
}])
.directive('torrentsTable', [function() {
return {
restrict: 'E',
templateUrl: 'templates/directives/torrentsTable.html',
scope: {
index: '='
},
controller: 'torrentController as torrentCtrl'
};
}]);
The idea is to use this directive to show different list of torrents with this syntax:
<torrents-table index="recommended"></torrents-table>
<torrents-table index="someOtherIndex"></torrents-table>
I wish this 2 almost same lines to show different "list" with results.
templates/directives/torrentsTable.html
<!-- I also tried with ng-repeat="torrent in torrentCtrl.recommended" -->
<!-- And is working as I excepted (It's shows the recommended array) -->
<div layout="row" ng-repeat="torrent in torrentCtrl[index]">
<div flex>Name: {{torrent.name}}</div>
<div flex>{{index}}</div>
</div>
{{index}} is not showing, and it's value is not showing.
While I actually make hardcoded ng-repeat arguments - it repeating but {{index}} is empty.
What I am doing wrong?
Your problem: how you pass key.
You use in directive:
scope: {
index: '='
},
so you should pass to directive expression, that evaluated to $scope property. So if you not inject scope - you pass undefined.
You can fix this two ways:
1) pass string instead something else
<torrents-table index="'recommended'"></torrents-table>
<torrents-table index="'someOtherIndex'"></torrents-table>
2) change directive definition to
scope: {
index: '#'
},
sample you can see in snippet below.
angular.module('app', [])
.controller('torrentController', [function() {
this.recommended = [1,2,3,4,5];
this.someOtherIndex = ['a','b','c','d','e'];
}])
.directive('torrentsTable', [function() {
return {
restrict: 'E',
template: '<div flex>{{index}}</div>'+
'<div layout="row" ng-repeat="torrent in torrentCtrl[index]">'+
' <div flex>Name: {{torrent}}</div>'+
'</div>',
scope: {
index: '='
},
controller: 'torrentController as torrentCtrl'
};
}])
.directive('torrentsTable2', [function() {
return {
restrict: 'E',
template: '<div flex>{{index}}</div>'+
'<div layout="row" ng-repeat="torrent in torrentCtrl[index]">'+
' <div flex>Name: {{torrent}}</div>'+
'</div>',
scope: {
index: '#'
},
controller: 'torrentController as torrentCtrl'
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<div ng-app='app'>
<torrents-table index="'recommended'"></torrents-table>
<torrents-table index="'someOtherIndex'"></torrents-table>
<hr/>
<torrents-table2 index="recommended"></torrents-table2>
<torrents-table2 index="someOtherIndex"></torrents-table2>
</div>
I have trouble getting my transcluding directive to work. I want to do the following: Create a directive that outputs a list where the content of each item is defined by transcluded content. E.g:
<op-list items="myItems">
<span class="item">{{item.title}}</span>
</op-list>
so I would use ng-repeat inside op-list's template and must be able to access the scope created by ng-repeat inside the transcluded content.
This is what I've done so far:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope', function ($scope) {
$scope.myModel = {
name: 'Superhero',
items: [{
title: 'item 1'
}, {
title: 'item 2'
}]
};
}]);
myApp.directive('opList', function () {
return {
template: '<div>' +
'<div>items ({{items.length}}):</div>' +
'<div ng-transclude ng-repeat="item in items"></div>' +
'</div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
items: '='
}
};
});
<html ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="MyCtrl">
<div>Hello, {{myModel.name}}!</div>
<op-list items="myModel.items">
<span>title: {{item.title}}|{{$scope}}|{{scope}}|{{items}}</span>
</op-list>
</div>
</html>
Check if this works:
myApp.directive('opList', ['$scope', function ($scope) {
return {
template: '<div>' +
'<div>items ({{model.items.length}}):</div>' +
'<ng-transclude ng-repeat="item in model.items"></ng-transclude>' +
'</div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
items: '='
}
};
}]);
If it doesn't then try to inspect $scope in console and see if you're able to access your model.
I want to pass object array to directive and have it print out the fields which I determine at the place where I use that directive.
Here's the example:
//directive
app.directive('MyDirective', function() {
return {
restrict: 'A',
templateUrl: 'my-directive.html',
scope: {
items: '#',
field: '#'
}
};
});
// my-directive.html template
<div ng-repeat="item in items">{{ item.field }}</div>
The idea is that I could use it with any object like this:
// object arrays
var phones = [{id:1,number:'555-5555'}, {id:2,number:'555-6666'}];
var persons = [{id:1,name:'John'}, {id:2,name:'Jane'}];
// directive usage
<div my-directive items="phones" data-field="???number???"></div>
<div my-directive items="persons" data-field="???name???"></div>
The result should print out numbers and names. Is that even doable in Javascript?
You can, just bind the items with '=':
.directive('myDirective', function() {
return {
restrict: 'A',
template: '<div ng-repeat="item in items">{{ item[field] }}</div>',
scope: {
items: '=',
field: '#'
}
};
})
Then use it like this:
<div my-directive items="phones" field="number"></div>
See this plunker.
Yes, it's possible, you can do it like this:
Directive:
myApp.directive('myDirective', function() {
return {
restrict: 'A',
template: '<div ng-repeat="item in items">{{ getItemField(item) }}</div>',
scope: {
items: '=',
field: '#'
},
link: function(scope, element, attr) {
scope.getItemField = function (item) {
return item[scope.field];
};
}
};
HTML:
<div my-directive items="phones" data-field="number"></div>
<div my-directive items="persons" data-field="name"></div>
Fiddle
This doesn't take a directive, probably the directive you are looking for is ng-repeat:
var phones = [{id:1,number:'555-5555'}, {id:2,number:'555-6666'}];
var persons = [{id:1,name:'John'}, {id:2,name:'Jane'}];
<li ng-repeat="phone in phones">{{phone.number}}</li>
<li ng-repeat="person in persons">{{person.name}}</li>