New to AngularJS I have a simple directive, service and controller. I loop through a list of items from a database in the controller embedded in the directive to render a checkbox list of items. From a form I am able to update my list of items in the database. I would at the same time like to update my list of displayed items with the newly added item and was hoping to benefit from Angulars two-way binding but I can't figure out how...
My directive:
angular.module('myModule').directive('menu', function (menuService) {
return {
restrict: 'E',
templateUrl: '../templates/menu.html',
controller: function () {
var me = this;
menuService.getMenuItems().then(function(data) {
me.items = data;
});
},
controllerAs: 'menu'
};
});
and corresponding html:
<div ng-repeat="item in menu.items">
<div class="col-md-4" ng-if="item.menuItem">
<div class="checkbox">
<label for="{{item._id}}">
<input id="{{item._id}}" type="checkbox" name="menuItems" ng-model="menuItems[$index]" ng-true-value="{{item._id}}">
{{item.menuItem}}
</label>
</div>
</div>
</div>
My issue is now that if I add a new item using this controller
EDIT: On my page I have two things. 1: A list of items rendered using the directive above and 2: A simple form with a single input field. I enter a new menuItem and click "Save". That triggers the controller below to be called with the new menuItem. The menuItem is then added to a collection in my database but I would like the list on my page to update with the newly added item. Preferably without having to reload the entire page.
$scope.insertMenuItem = function () {
$http.post('/menuItems', $scope.formData)
.success(function (data) {
$scope.items = data;
});
};
then the "me.items" in my directive remains unchanged hence so does my list in my browser.
How do I "tie it all together" so that when I call insertMenuItem then my.items are updated automagically?
This didn't come out as well as I had hoped but I hope you get the meaning...
Thanks in advance
take a look at this http://jsbin.com/rozexebi/1/edit, it shows how to bind a list of items in a directive to a controller, hope it helps.
Related
I am working on creating reusable directive which will be showing composite hierarchical data .
On first page load, Categories like "Server" / "Software"/ "Motherboard" (items array bound to ng-repeat) would be displayed . If user clicks on "Server" then it would show available servers like "Ser1"/"Ser2"/"Ser3".
html :
<div ng-app="myApp" ng-controller="myCtrl" ng-init="init()">
<ul>
<li ng-repeat="item in items">
<div my-dir paramitem="item"></div>
</li>
</ul>
</div>
Now first time Items are loading, but clicking on any item is not refreshing ng-repeat. I have checked ng-click, "subItemClick" in below controller, method and it is being fired. However the items collection is not getting refreshed.
http://plnkr.co/edit/rZk9cbEJU90oupVgcSQt
Controller:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.init = function() {
$scope.items = [{iname: 'server',subItems: ['ser1', 'ser2','ser3']}
];
};
$scope.subItemClick = function(sb) {
if (sb.subItems.length > 0) {
var zdupitems = [];
for (var i = 0; i < sb.subItems.length; i++) {
zdupitems.push({
iname: sb.subItems[i],
subItems: []
});
}
$scope.items = zdupitems;
}
};
}])
.directive('myDir', function() {
return {
controller: 'myCtrl',
template: "<div><a href=# ng-click='subItemClick(paramitem)'>{{paramitem.iname}}</a></div>",
scope: {
paramitem: '='
}
}
});
I am expecting items like ser1/ser2 to be bound to ng-repeat on clicking "Server" but it is not happening .
Any help?
I think that onClick is screwing up the method's definition of $scope. In that context, the $scope that renders the ngRepeat is actually $scope.$parent (do not use $scope.$parent), and you're creating a new items array on the wrong $scope.
I realize the jsfiddle is probably a dumbed down example of what you're dealing with, but it's the wrong approach either way. If you need to use a global value, you should be getting it from an injected Service so that if one component resets a value that new value is reflected everywhere. Or you could just not put that onClick element in a separate Directive. What's the value in that?
I am trying to make custom directive in angular .I try to add input field in my view when I click on button .In other words I am trying to make one custom directive in which when user press the button it add one input field in the browser .I think it is too easy if I am not use custom directive Mean If I use only controller then I take one array and push item in array when user click on button and button click is present on controller.
But when need to make custom directive where I will write my button click event in controller or directive
here is my code
http://play.ionic.io/app/23ec466dac1d
angular.module('app', ['ionic']).controller('appcontrl',function($scope){
$scope.data=[]
}).directive('inputbutton',function(){
return {
restrict :'E',
scope:{
data:'='
},
template:'<button>Add input</button> <div ng-repeat="d in data"><input type="text"></div>',
link:function(s,e,a){
e.bind('click',function(){
s.data.push({})
})
}
}
})
I just need to add input field when user click on button using custom directive ..could you please tell me where i am doing wrong ?
can we make button template and click event inside the directive
The reason it doesn't work is because your registering your click handler with jQuery. So when the click handler fires it is out of the scope of angular so angular does not know it needs to update its bindings.
So you have two options, the first is to tell angular in the click handler, 'yo buddy, update your bindings'. this is done using $scope.$apply
$apply docs: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
e.bind('click',function(){
s.$apply(function() {
s.data.push({});
});
});
However angular already has built in directive for handling things like mouse clicks you can just use that and let angular do the work for you. This would be the better option.
so first in your view register a click handler on your button
<button ng-click="add()">Add input</button> <div ng-repeat="d in data"><input type="text"></div>
Then in your link simply add the add() method of your scope
s.add = function () {
s.data.push({});
}
Heres a working fiddle showing both examples. http://jsfiddle.net/3dgdrvkq/
EDIT: Also noticed a slight bug in your initial click handler. You registering a click but not specifying the button to apply it to. So if you clicked anywhere in the directive, not just the button, the handler would fire. You should be more specific when registering events manually, using ids, class names attributes etc.
The e or element property of the link function is a jqlite or full jQuery object of the entire directive. If you have jQuery included before angular it will be a full jQuery object. If not it will a jqlite object. A thinned out version of jQuery.
Here is a basic example for your logic .
var TestApp = angular.module('App', []);
// controller
TestApp.controller('mainCtrl', function mainCtrl($scope) {
$scope.data = [];
$scope.addDataItem = function () {
$scope.data.push({
someFilield: 'some value'
});
console.log('pushing value ... ');
}
});
// view
<div ng-app="App" class="container" ng-controller="mainCtrl">
<button type="button" ng-click="addDataItem()">Add an input</button>
<div ng-repeat="d in data track by $index">
<custom-directive model="d"></custom-directive>
</div>
</div>
// directive
TestApp.directive('customDirective', function customDirective() {
return {
restrict: 'E',
scope: {
model: '='
},
template: 'item -> <input type = "text" />',
link: function (scope, elem, attrs) {
console.log('scope.model', scope.model);
},
controller: function ($scope) {
// do staff here
}
}
});
Since I am a beginner using AngularJS the $scope approach to pass data between different controllers and (in my case) a modal drives me crazy. Due to that reason, I googled around the web and found an interesting blogpost about passing data to a UI-bootstrap modal without using $scope.
I had a deeper look at this blogpost and the delivered plunk which works pretty nice and started to adopt this to my own needs.
What I want to achieve is to open a modal delivering an text input in which the user is able to change the description of a given product. Since this would provide more than a minimal working example I just broke everything down to a relatively small code snippet available in this plunk.
Passing data from the main controller into the modal seems to work as the default product description is displayed in the modal text input as desired. However, passing the data back from the modal to the main controller displaying the data in index.html does not seem to work, since the old description is shown there after it was edited in the modal.
To summarize my two questions are:
What am I doing wrong in oder to achieve a 'two-way-binding' from the main controller into the modal's text input and the whole way back since the same approach works in the mentioned blogpost (well, as the approach shown in the blogpost works there must be something wrong with my code, but I cannot find the mistakes)
How can I implement a proper Accept button in order to accept the changed description only if this button is clicked and discard any changes in any other case (clicking on Cancel button or closing the modal by clicking next to it)?
In your main controller, create two resolver functions: getDescription and setDescription.
In your modal controller, use them.
Your modal HTML
<div class="modal-header">
<h3 class="modal-title">Test Text Input in Modal</h3>
</div>
<div class="modal-body">
Product description:
<input type="text" ng-model="modal.description">
</div>
<div class="modal-footer">
<button ng-click="modal.acceptModal()">Accept</button>
<button ng-click="modal.$close()">Cancel</button>
</div>
Your main controller
function MainCtrl($modal) {
var self = this;
self.description = "Default product description";
self.DescriptionModal = function() {
$modal.open({
templateUrl: 'modal.html',
controller: ['$modalInstance',
'getDescription',
'setDescription',
ModalCtrl
],
controllerAs: 'modal',
resolve: {
getDescription: function() {
return function() { return self.description; };
},
setDescription: function() {
return function(value) { self.description = value; };
}
}
});
};
};
Your modal controller
function ModalCtrl($modalInstance, getDescription, setDescription) {
var self = this;
this.description = getDescription();
this.acceptModal = function() {
setDescription(self.description);
$modalInstance.close();
};
}
I have a directive that displays a list of "master" items and when the user clicks on one of these items I want any "details" directives on the page (there could be more than one) to be updated with the details of the currently selected "master" item.
Currently I'm using id and href attributes as a way for a "details" directive to find its corresponding master directive. But my impression is that this is not the angular way, so if it's not, what would be a better solution?
I appreciate that typically when the issue of inter-communication between directives is raised then the obvious solutions are either to use require: "^master-directive" or to use a service, but in this case the directives are not in the same hierarchy and I don't think using a service is appropriate, as it would make the solution more complicated.
This is some illustrative code showing what I'm doing currently.
<div>
<master-list id="master1"></master-list>
</div>
<div>
<details-item href="#master1" ></details-item>
</div>
In the master-list directive when an item is selected I set an attribute to indicate the currently selected master item:
attrs.$set('masterListItemId',item.id);
In the details-item directive's link function I do:
if (attrs.href) {
var id = attrs.href.split('#')[1];
var masterList = angular.element(document.getElementById(id));
if (masterList) {
var ctrl = masterList.controller('masterList');
ctrl.attrs().$observe('masterListItemId',function(value) {
attrs.$set('detailItemId',value);
});
}
}
attrs.$observe('detailItemId',function(id) {
// detail id changed so refresh
});
One aspect that put me off from using a service for inter-directive communication was that it is possible (in my situation) to have multiple 'masterList' elements on the same page and if these were logically related to the same service, the service would end up managing the selection state of multiple masterList elements. If you then consider each masterList element had an associated detailItem how are the right detailItem elements updated to reflect the state of its associated masterList?
<div>
<master-list id="master1"></master-list>
</div>
<div>
<master-list id="master2"></master-list>
</div>
<div>
<details-item href="#master1" ></details-item>
</div>
<div>
<details-item href="#master2" ></details-item>
</div>
Finally I was trying to use directives, rather than using controller code (as has been sensibly suggested) as I'd really like the relationship between a masterList and its associated detailItems to be 'declared' in the html, rather than javascript, so it is obvious how the elements relate to each other by looking at the html alone.
This is particularly important as I have users that have sufficient knowledge to create a html ui using directives, but understanding javascript is a step too far.
Is there a better way of achieving the same thing that is more aligned with the angular way of doing things?
I think I would use a service for this. The service would hold the details data you care about, so it would look something like this.
In your master-list template, you might have something like a list of items:
<ul>
<li ng-repeat"item in items"><a ng-click="select(item)">{{item.name}}</a></li>
</ul>
...or similar.
Then in your directives, you would have (partial code only)
.directive('masterList',function(DetailsService) {
return {
controller: function($scope) {
$scope.select = function(item) {
DetailsService.pick(item); // or however you get and retrieve data
};
}
};
})
.directive('detailsItem',function(DetailsService) {
return {
controller: function($scope) { // you could do this in the link as well
$scope.data = DetailsService.item;
}
};
})
And then use data in your details template:
<div>Details for {{data.name}}</div>
<ul>
<li ng-repeat="detail in data.details">{{detail.description}}</li>
</ul>
Or something like that.
I would not use id or href, instead use a service to retrieve, save and pass the info.
EDIT:
Here is a jsfiddle that does it between 2 controllers but a directive would be the same idea
http://jsfiddle.net/u3u5kte7/
EDIT:
If you want to have multiple masters and details, leave the templates unchanged, but change your directive controllers and services as follows:
.directive('masterList',function(DetailsService) {
return {
controller: function($scope) {
$scope.select = function(item) {
DetailsService.pick($scope.listId,item); // or however you get and retrieve data
};
}
};
})
.directive('detailsItem',function(DetailsService) {
return {
controller: function($scope) { // you could do this in the link as well
$scope.data = DetailsService.get($scope.listId).item;
}
};
})
.factory('DetailsService',function(){
var data = {};
return {
pick: function(id,item) {
data[id] = data[id] || {item:{}};
// set data[id].item to whatever you want here
},
get: function(id) {
data[id] = data[id] || {item:{}};
return data[id];
}
};
})
I would opt for a different approach altogether without directives. Directives are ideal for DOM manipulation. But in this case I would stick to using just the template and a controller that manages all the data and get rid of the directives. Use ng-repeat to repeat the items
Check out this fiddle for an example of this: http://jsfiddle.net/wbrand/2xrne4k3
template:
<div ng-controller="ItemController as ic">
Masterlist:
<ul><li ng-repeat="item in ic.items" ng-click="ic.selected($index)">{{item.prop1}}</li></ul>
Detaillist:
<ul><li ng-repeat="item in ic.items" >
{{item.prop1}}
<span ng-if="item.selected">SELECTED!</span>
</li></ul>
</div>
controller:
angular.module('app',[]).controller('ItemController',function(){
this.items = [{prop1:'some value'},{prop1:'some other value'}]
this.selectedItemIndex;
this.selected = function(index){
this.items.forEach(function(item){
item.selected = false;
})
this.items[index].selected = true
}
})
I have a standard Angular controller invoked with appropriate routing:
when('/admin/foo/new',{
controller:FooNewCtrl,
templateUrl: 'frontend/partials/admin/foo-new.html'
}).
FooNewCtrl, nothing in it:
function FooNewCtrl($scope) {
}
In foo-new.html I have a section where I want the user to be able to dynamically add and delete input fields:
<label>Fields<i class="foundicon-plus"></i></label>
<div ng-repeat="field in fields">
<input type="text" id="field.id"/>delete
</div>
I do not know how to make this happen. Note that I have a link, with a "plus" icon, used to add the new field, then when the field is displayed, a link that would delete it.
I do not know how to control "fields" with a controller and keep the user on the page.
I'm looking for a push in some direction.
In your ng-repeat you're basically saying 'for every field in the fields collection, located in this scope'.
function FooNewCtrl($scope) {
//What you're iterating over in your ng-repeat
$scope.fields = [];
//Declare a function on the scope that you can reference from your template
$scope.addField = function(){
$scope.fields.push(1); //Push a new object into the fields
};
}
So yeah you'd add an ng-click event to your element (parent of your icon), that pushed a new field onto fields.
<label>Fields<a ng-click="addField()"><i class="foundicon-plus"></i></a></label>
<div ng-repeat="field in fields">
<input type="text" id="field.id"/>delete
</div>
Clicking the plus icon should now dynamically add your inputs. I'll leave removing them to you.