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?
Related
I want to update the scope inside a directive that is outside of my main view, here's my code:
index.html
<!-- the directive I want to update -->
<nav sitepicker></nav>
<section id="content-wrapper">
<!-- main content-->
<div ui-view></div>
</section>
sitepicker is essentialy just a dropdown menu that contains some html structure.
sitepicker.html
<span>{{currentWebsite}}</span> <-- this is the one I want to update
<ul>
<li ng-repeat="website in websites">{{website.name}}</li>
</ul>
and the JS:
.controller('sitepicker', function($scope, websiteService)
$scope.website = websiteService.currentWebsite; // not updating eventhough I update this in overview.js
});
overview.js
.controller('OverviewCtrl', function($scope, websiteService) {
websiteService.currentWebsite = website; // assume that this value is dynamic
});
but currentWebsite is not changing. How can I work around this? I want to avoid using $rootScope because I know it's bad.
Here's my service:
.factory('websiteService', function() {
var currentWebsite;
return {
currentWebsite: currentWebsite
};
});
Edit: Adding a watch like this works but i'm not sure if its good
.controller('sitepicker', function($scope, websiteService)
$scope.$watch(function() {
$scope.website = websiteService.currentWebsite;
});
});
Solution #1
We can add $watch to makes this possible
.controller('sitepicker', function($scope, websiteService)
$scope.$watch(function(){
return websiteService.currentWebsite;
}, function(newValue){
// Do something with the new value
});
});
Solution #2
We can also define websites in our main controller. Then, we can update it in our overview child controller like so:
.controller('sitepicker', function($scope, websiteService)
$scope.$parent.website = websiteService.currentWebsite;
});
it is important to use $parent
Since we update the controller that wraps up the whole app, we will be able to access it from anywhere, any directive, controller, view, etc.
I have two views, the first one (Search) has a button which when clicked will add an item to $scope.results1 and then take the user to the other view (Results) where the ng-repeat is.
When I click the button and the results page comes up, only "1" is displayed. However, if I call the test function straight away in the controller, I get taken to the Results page and both "1" and "2" are displayed. In both cases, the console log shows that the array results1 contains 2 items.
From what I've read, the solution would be to implement either a factory or a service but I'm fairly new to Ionic/angular so not quite sure how to begin such an implementation, any pointers would be appreciated!
Button in Search view :
<button class="button-full" id="find" ng-click="test();">Find</button>
SearchController:
$scope.results1=[];
$scope.results1.push(1);
$scope.test = function(){
$scope.results1.push(2);
console.log("pushed 2");
console.log($scope.results1);
$state.go("tab.results");
};
Results view:
<ion-content ng-controller="SearchController">
<body>
<div id="results">
<div class="list" id="search-items">
<div ng-repeat="item in results1">
{{item}}
</div>
</div>
</div>
</body>
</ion-content>
You could implememnt a service for holding Results like this
var mainApplicationModule = angular.module("yourAppName");
mainApplicationModule.service('ResultService', function(){
var results = [];
this.add = function(data){ // to add data to results
results.push(data);
}
this.getResults = function(){ // to get all results
return(results);
}
})
Inject ResultService into your SearchController like this,
mainApplicationModule.controller('SearchController',['$scope','ResultService','$location', function($scope,ResultService,$location) {
ResultService.add(1) // Adds 1 to 'results' array in ResultService
$scope.test = function() {
ResultService.add(2); // Adds 2 to Results array in ResultService
$location.path("/results") // replace with path to your results view
}
$scope.results1 = ResultService.getResults(); // will have [1,2]
}
you can pass data while changing state,
config the state like this:
.state('tab.results', {
url: '/yoururl',
templateUrl: 'yourtemplate',
controller: 'yourcontroller',
params: {
"results": ""
}
});
then using :
$state.go("tab.results",{"results": $scope.results1});
in the second controller inject $stateParams and get value:
$scope.results = $stateParams.results;
I have a following controller
app.controller('MainController', function($scope, $interval,$mdToast, $document, $mdDialog,$timeout,$mdDialog) {
var stops=[
{
stopName:"testinput1",
noOfStudents:2
},
{
stopName:"testinput2",
noOfStudents:2
},
{
stopName:"testinput3",
noOfStudents:4
}
];
$scope.list=stops;
$scope.addStop=function(name,noOfstudent){
stops.push({
stopName:name,
noODstudent:noOfstudent
})
$scope.list=stops;
}
});
in my view I have following code,
<md-list id="stopList">
<md-list-item class="md-3-line" ng-repeat="item in list" style="background:rgb(233, 233, 233);margin:10px;padding-left: 10px;position: relative;min-height: 60px;">
<div class="md-list-item-text">
<h3>{{item.stopName}}</h3>
<h4>{{item.noOfStudents}}</h4>
</div>
<div ng-show="deleteIcon" ng-click="showConfirm($event);" class='delete_icon'></div>
</md-list-item>
</md-list>
The issue I am facing is when I add a stop, the ng-repeat list does not get updated. I want the view to be updated as I add a stop. I am taking the user input from angular material dialog.
Data will be updated automatically in view after you update it in controller. What problem (may be ) you are facing is typo in addStop function.
You have used two dots when updating list. >> $scope..list=stops;
You don't need to push to stop
Just direct push to $scope.list
When stop assigned in the list it'll assign reference if one is updated another will also
$scope.list=stops;
Like this
$scope.list.push({
stopName: name,
noODstudent: noOfstudent
})
Here is a plnkr:
http://plnkr.co/edit/HlzxQ9sqbMbxDiraT22z?p=preview
Seems to be working for me
var name = 'l'
var noOfStudents = 5
$scope.addStop=function(){
stops.push({
stopName:name,
noOfStudents:noOfStudents
})
$scope.list=stops;
}
i have used static data but there should not be any problem
Try this
$scope.addStop = function (name, noOfstudent) {
stops.push({
stopName: name,
noODstudent: noOfstudent
});
$timeout(function () {
$scope.list = [];
$scope.list = stops;
}, 0);
};
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'm pretty new to angular js but it seems like my simple code should work. Here is the html:
<body ng-app="MyHomepage">
<div ng-controller="RedditLoad">
{{a}}
<ul>
<li ng-repeat="article in a">
{{article.data.title}}
</li....
and here is my angualr_app.js:
var App = angular.module('MyHomepage', [])
function RedditLoad($scope){
$.getJSON("http://www.reddit.com/.json?jsonp=?", function(data) {
var data_array = [];
$.each(data.data.children, function(i,item){
data_array.push(item);
});
console.log(data_array);
$scope.a = data_array;
});
}
What am I doing wrong? console.log(data_array); is showing the correct values but the data wont seem to get passed to the template.
The getJSON callback isn't executed in the angular context so angular doesn't know about your changes and won't refresh the bindings. When code is called from an external source (like a jQuery event ), you have to encapsulate your scope changes in an $apply call:
$scope.$apply(function{
$scope.a = data_array;
}