In this plunk I have a table with ng-repeat, where one of the elements in the table is a directive. Problem is that directive field is not showing in the table. What's wrong with this code?
HTML
<table border="1">
<tr ng-repeat="row in rows">
<td>
{{row.x}}
</td>
<td>
<div some-directive field="row.field"></div>
</td>
</tr>
</table>
Javascript
var app = angular.module('app', []);
app.controller('myCtl', function($scope) {
$scope.rows = [{x: 1}, {x:2}];
});
app.directive('someDirective', function () {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
field: '='
};
directive.link = function (scope, element, attrs) {
scope.field = "aaa";
};
return directive;
});
The answer is to add directive.template = "{{field}}";
Related
I have a directive that seems to set everything properly when Angular instantiates the page, but doesn't update when I interact with its components.
HTML :
<div class="app-table col-md-6 col-sm-12 col-xs-12" app-table>
<header>
<span class="col-xs-12">Subscriptions</span>
</header>
<div class="pagination">
<span class="user-controls">
<span class="page-down glyphicon glyphicon-chevron-left"></span>
<input type="text" ng-model="page"/>
<span>/ {{summary.subscriptions.pages}}</span>
<span class="page-up glyphicon glyphicon-chevron-right"></span>
</span>
<span class="results-text">Results 1 - {{summary.subscriptions.labels.length}} of {{summary.subscriptions.total}}</span>
</div>
<table class="col-xs-12 table-striped">
<thead>
<td class="col-xs-9">Name</td>
<td class="col-xs-3">Subscribers</td>
</thead>
<tbody>
<tr ng-repeat="label in summary.subscriptions.labels | limitTo: 5 : offset">
<td class="col-xs-9">{{label.name}}</td>
<td class="col-xs-3">{{label.subscribers}}</td>
</tr>
</tbody>
</table>
</div>
Controller:
app.controller('DashboardCtrl', ['$scope', 'DashboardService', function($scope, DashboardService) {
$scope.summary = {};
$scope.init = function() {
DashboardService.getSummary().then(function(response){
$scope.summary = response;
});
};
$scope.init();
}]);
Directive:
app.directive('appTable', [function () {
return {
restrict: 'A',
scope: true,
link: function (scope, elem) {
scope.offset = 0;
scope.page = 1;
elem.find('.page-up').bind('click', function(){
scope.offset += 5;
scope.page += 1;
});
elem.find('.page-down').bind('click', function(){
scope.offset -= 5;
scope.page -= 1;
});
}
};
}]);
When the page loads it correctly shows page 1 with an offset of 0. If I change the variable to page=2 and offset=5 then the page loads as would be expected, the values are populated correctly, and the offset correctly shows the subscriptions for indexes 6-10. However, when I click the buttons that the click elements are bound to I don't see the page or offset variables update, even though I can verify through the Chrome Dev Tools that the click bindings are being hit. It seems the directive is not passing the scope variables to the parent controller properly?
This is still issue about using jquery in angularjs.
In fact offset and page are already changed in your scope, but if you want to reflect them on your view, you have to call scope.$apply() to let angular rerender your page.
var app = angular.module("app", []);
app.controller('DashboardCtrl', ['$scope', function($scope) {
$scope.summary = {
subscriptions: {
labels: [
{
name: 'name1',
subscribers: 'A,B'
},{
name: 'name2',
subscribers: 'A,B,C'
},{
name: 'name3',
subscribers: 'A,B,C,D'
}
],
total: 10,
pages: 1
}
};
//$scope.offset = 0;
//$scope.page = 1;
}]);
app.directive('appTable', [function() {
return {
restrict: 'A',
scope: true,
link: function(scope, elem) {
scope.offset = 0;
scope.page = 1;
elem.find('.page-up').on('click', function() {
scope.$apply(function() {
scope.offset += 5;
scope.page += 1;
});
});
elem.find('.page-down').on('click', function() {
scope.$apply(function() {
scope.offset -= 5;
scope.page -= 1;
});
});
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="app" ng-controller="DashboardCtrl">
<div class="app-table col-md-6 col-sm-12 col-xs-12" app-table>
<header>
<span class="col-xs-12">Subscriptions</span>
</header>
<div class="pagination">
<span class="user-controls">
<span class="page-down glyphicon glyphicon-chevron-left">PageDown</span><br>
<input type="text" ng-model="page" />
<span>/ {{summary.subscriptions.pages}}</span><br>
<span class="page-up glyphicon glyphicon-chevron-right">PageUp</span><br>
</span>
<span class="results-text">Results 1 - {{summary.subscriptions.labels.length}} of {{summary.subscriptions.total}}</span>
</div>
<table class="col-xs-12 table-striped">
<thead>
<td class="col-xs-9">Name</td>
<td class="col-xs-3">Subscribers</td>
</thead>
<tbody>
<tr ng-repeat="label in summary.subscriptions.labels | limitTo: 5 : offset">
<td class="col-xs-9">{{label.name}}</td>
<td class="col-xs-3">{{label.subscribers}}</td>
</tr>
</tbody>
</table>
{{offset}}
{{page}}
</div>
I think you have 2 separate issues.
The first issue is that you are calling a click handler outside of Angular, so it doesn't know it needs to run its digest cycle. You can solve this by attaching ng-click on your page-up and page-down elements, or by calling $apply:
elem.find('.page-up').bind('click', function(){
scope.$apply(function () {
scope.offset += 5;
scope.page += 1;
});
});
The second issue is that your directive has scope: true which means it is inherting the parent (controller) scope via prototypal inheritance. So when your directive starts to set page and offset onto the scope, it creates new variables on its own scope, which any outer scope like the controller doesn't see:
link: function (scope, elem) {
// Creating new variables on the child scope
scope.offset = 0;
scope.page = 1;
elem.find('.page-up').bind('click', function(){
// Updating variables on the child scope
scope.offset += 5;
scope.page += 1;
});
}
So if you add {{ page }} and {{ offset }} markup inside the app-table element, you'll see it update. But if you add that markup outside of the app-table element (but still inside your ng-controller element) you will see that the controller does not have the new information.
It should work if you set scope: false, because then you'd be writing to the same exact scope as the controller.
It can work with scope: true if you use objects in your controller instead:
scope.pagination = {
offset: 0;
page: 1
};
Also, I'm not sure if the example presented here is simplified, but you are basically using an Angular directive to do jQuery. Have you considered making an independent pagination component, with scope: "=" (isolate scope)? Then you could explicitly pass in the data it needs (page, offset, total, .etc), and the directive could have its own template.
I've been trying to call passID() function when page initializes but the function still doesn't work.
The website works this way:
Page-A passes data to Controller-A
then to Service then to Controller-B then the function from Controller-B gets called by Page-B.
My guess is that my error is in Page-B since I've successfully passed the data of Page-A up to Controller-B but I'm not 100% sure.
In my Page-A, I've made a button calling the function of Controller-A using ng-Click
<button class="viewDetails" ng-click="passID([1,03,07])">test button</button>
Controller-A only gets data from Page-A
angular.module('handsetExplorerModule')
.controller("compareHandsetController", ['$scope','compareHandsetService','sharedData', function($scope, compareHandsetService, sharedData) {
$scope.passID = function(id){
var arrayID = [];
arrayID = id;
sharedData.set(arrayID);
};
}]);
Service gets my JSON and is my getter and setter
angular.module('handsetExplorerModule')
.factory('compareDetailsService', ['$http','$q', function($http, $q) {
return {
getHandsetList: function(){
return $http.get("./data/compareJSON.json");
}
};
}]);
angular.module('handsetExplorerModule')
.factory('sharedData', function() {
var savedData = {}
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
Controller-B filters the JSON file to only get what I need (i used ID from Page-A to filter)
angular.module('handsetExplorerModule')
.controller("compareDetailsController", ['$scope','compareDetailsService','sharedData', function($scope, compareDetailsService, sharedData) {
$scope.data = {phones: []};
compareDetailsService.getHandsetList()
.then(
function(data){
$scope.data.phones = data.data;
},
function(error){
//todo: handle error
}
);
var getData = sharedData.get();
$scope.passID = function passID(){
var newData = [];
for(var i=0; i<$scope.data.phones.length; i++){
if($scope.data.phones[i].id == getData[0] || $scope.data.phones[i].id == getData[01] || $scope.data.phones[i].id == getData[02]){
newData.push($scope.data.phones[i]);
}
}
$scope.phoneContent = newData;
}
$scope.viewDetails = function(id){
alert(id);
}
}]);
Lastly, In my Page-B, I've called Controller-B function: <div ng-init="passID()">
to be used by ng-repeat of my Page-B:
<table id="Content" ng-repeat="x in phoneContent track by x.id">
<tr>
<td class="one">
<table>
<tr>
<td><img class="contImage" ng-src="{{x.image}}" ng-alt="{{x.name}}" /></td>
<td class="textAlign">{{x.name}} <button class="viewDetails" ng-click="viewDetails(x.id)">VIEW DETAILS</button></td>
</table>
</td>
<td class="two">{{x.size}}</td>
<td class="one">{{x.storage}}</td>
<td class="two">{{x.camera}}</td>
<td class="one">{{x.battery}}</td>
<td class="two">{{x.network}}</td>
<td class="one">{{x.sim}}</td>
</tr>
</table>
I don't know why you define arrayID.
To solve your problem try with sharedData.set = arrayID; instead of sharedData.set(arrayID);.
Hope it helps!
I have angular application which uses directives.
In the directive I have template that defines pop up modal.
Basically, it's very simple app that shows a list of book authors, and in the list there is an Edit button that opens modal box.
If I open the modal for editing the book author, and just close it, without editing the author - there is no problem.
But if I open the modal, and type something in the author input, and close it, the model is stuck with the current input value for the whole time, so if I open another author for editing, the model will not be updated.
My question is: why is this happening, and how to fix it ?
HTML
<div ng-controller="MyCtrl">
<table class="table table-hover">
<tr>
<td><b>Publisher</b></td>
<td><b>Edit Publisher</b></td>
</tr>
<tr ng-repeat="book in books">
<td>
{{book.Author}}
</td>
<td>
<span ng-click="toggleModal(book)" class="btn btn-primary">Edit</span>
</td>
</tr>
</table>
<modal-dialog info='modalShown' show='modalShown' width='600px' height='60%'>
<div ng-show="divBook">
<input type="text" name="bookAuthor" ng-model="bookAuthor" />
</div>
</modal-dialog>
</div>
Angular
var myApp = angular.module('myApp',[]);
myApp.controller("MyCtrl", function($scope){
$scope.books = [{Author:"Jon Skeet"},{Author:"John Papa"},{Author:"Scott Hanselman"}];
$scope.modalShown = false;
$scope.toggleModal = function (book) {
$scope.bookAuthor = book.Author;
$scope.modalShown = !$scope.modalShown;
$scope.divBook = true;
};
});
myApp.directive('modalDialog', function () {
return {
restrict: 'E',
template: "<div class='ng-modal' ng-show='show'>"
+"<div class='ng-modal-overlay' ng-click='hideModal()'>"
+"</div>"
+"<div class='ng-modal-dialog' ng-style='dialogStyle'>"
+"<div class='ng-modal-close' ng-click='hideModal()'>X</div>"
+"<div class='ng-modal-dialog-content' ng-transclude>"
+"</div>"
+"</div>"
+"div>",
replace: true,
scope: {
show: '=info'
},
transclude: true,
link: function (scope, element, attrs) {
//scope.apply();
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
scope.hideModal = function () {
scope.show = false;
};
}
};
});
So, the test case will be:
Click Edit -> change the value -> close the modal
Click Edit on another author.
JSFiddle: http://jsfiddle.net/HB7LU/17694/
The model value is changing, however you are creating a new variable and not modifying the original element of the array.
You can change that by putting a pointer of the array object in a scope variable
$scope.toggleModal = function (book) {
$scope.book = book;
$scope.modalShown = !$scope.modalShown;
$scope.divBook = true;
};
then pointing the ng-model to the .Author property of the object.
<input type="text" name="bookAuthor" ng-model="book.Author" />
See modified JSFiddle: http://jsfiddle.net/9a2jcc9u/1/
I have changed your JS fiddle, if you want to change name and it automatically changes in grid than remove angular.copy(book) and directly assign book. you can see your jsfiddle here jsfiddle
myApp.controller("MyCtrl", function($scope){
$scope.books = [{Author:"Jon Skeet"},{Author:"John Papa"},{Author:"Scott Hanselman"}];
$scope.modalShown = false;
$scope.toggleModal = function (book) {
$scope.book = angular.copy(book);
$scope.modalShown = !$scope.modalShown;
$scope.divBook = true;
};
});
your modal dialog
<modal-dialog info='modalShown' show='modalShown' width='600px' height='60%'>
<div ng-show="divBook">
<input type="text" name="bookAuthor" ng-model="book.Author" />
</div>
</modal-dialog>
try something like this
myApp.controller('MyCtrl', ['$scope',function($scope) {
//your code
}]);
I try to sort data with angular.
Here my code:
<table id="data-table" width="100%" class="table table-striped table-hover">
<thead>
<tr>
<th class="sortable" field="name">Name</th>
<th class="sortable" field="phone">Phone number</th>
</tr>
</thead>
<tbody>
<tr class="tr-data" ng-repeat="el in list | orderBy:sortCol:sortType" >
<td>{{el.name}}</td>
<td>{{el.phone}}</td>
</tr>
</tbody>
</table>
<div>{{sortType}}</div>
<div>{{sortCol}}</div>
javascript:
var someData = [
{'name': 'Vasja', 'phone': '00375 29 654 1185'},
{'name': 'Sasha', 'phone': '00375 29 654 1176'}];
app.controller('myCtrl', function($scope)
{
$scope.sortType = false;
$scope.sortCol = 'phone';
$scope.list = someData;
$scope.applySort = function()
{
$('th.sortable').each(function(idx, el)
{
var uarr = $('<span class="sort-span" ng-click="xSort(this);">↑</span>');
var darr = $('<span class="sort-span">↓</span>');
uarr.appendTo(el).on('click', function() { $scope.sortCol = $(el).attr('field'); $scope.sortType = false;});
darr.appendTo(el).on('click', function() { $scope.sortCol = $(el).attr('field'); $scope.sortType = true;});
});
};
$scope.applySort();
});
By clicking on arrow - nothing changed. Even data sortCol and SortType don't changed.
But, when i change data in list - sorting is applying;
Angular couldn't fire it's events by jquery events. Either you could add $scope.$apply() to the end of your jquery events, it would work but this isn't a good solution.
The better way is to render your arrows in angular and bind events with angular.
Don't use jQuery, it should be done with Angular directives like ngClick:
<thead>
<tr>
<th class="sortable" field="name">
Name
<span class="sort-span" ng-click="sort('name', false)">↑</span>
<span class="sort-span" ng-click="sort('name', true)">↓</span>
</th>
<th class="sortable" field="phone">
Phone number
<span class="sort-span" ng-click="sort('phone', false)">↑</span>
<span class="sort-span" ng-click="sort('phone', true)">↓</span>
</th>
</tr>
</thead>
and controller:
app.controller('myCtrl', function ($scope) {
$scope.sortType = false;
$scope.sortCol = 'phone';
$scope.list = someData;
$scope.sort = function (field, type) {
$scope.sortCol = field;
$scope.sortType = type;
};
});
I am trying to highlight multiple row select functionality, so that when a user click's on a row it highlight the row and when it again select that row it un-highlight that. But the problem I am facing is how to give array of items to ng-class.
Here is my code.
<tr ng-click="selectRow(1)" ng-class="{row_selected: ArrayOfItems}" class="ng-scope odd">
<td class="">
test
</td>
</tr>
And in my controller
$scope.selectedArray = [];
$scope.selectRow = function(id) {
if($scope.selectedArray.indexOf(id) == -1) {
$scope.selectedArray.push(id);
}else {
$scope.selectedArray.splice($scope.selectedArray.indexOf(id),1);
}
});
So what I am doing in controller is on clicking a row it push the id's in an array and on clicking the same row it pops out that id from array.
Any help ?
First check whether the row is selected or not
<tr ng-click="selectRow(1)" ng-class="{row_selected: isRowSelected(1)}" class="ng-scope odd">
<td class="">
test
</td>
</tr>
Add the isRowSelected to controller:
$scope.selectedArray = [];
$scope.isRowSelected = function(id) {
$scope.selectedArray.indexOf(id) != -1
}
$scope.selectRow = function(id) {
if($scope.selectedArray.indexOf(id) == -1) {
$scope.selectedArray.push(id);
}else {
$scope.selectedArray.splice($scope.selectedArray.indexOf(id),1);
}
});
More proper setup would be with in use of 'ng-repeat'. See the working code at:
JSFiddle
HTML:
<div ng-controller="MyController">
<table>
<tr ng-repeat="item in items" ng-click="selectRow(item)" ng-class="{row_selected: isSelected(item)}">
<td>
<a id="{{item}}">test {{item}}</a>
</td>
</tr>
</table>
</div>
JS/AngularJS:
var myApp = angular.module('myApp', []);
myApp.controller('MyController', ['$scope', function ($scope) {
$scope.items = [1,2,3,4,5];
$scope.selectedArray = [];
$scope.selectRow = function (id) {
if($scope.selectedArray.indexOf(id) == -1) {
$scope.selectedArray.push(id);
}else {
$scope.selectedArray.splice($scope.selectedArray.indexOf(id),1);
}
};
$scope.isSelected = function (id) {
if( $scope.selectedArray.indexOf(id) > -1){
return true;
}
return false;
};
}]);