I got this directive:
.directive('studentTable', [function() {
return {
restrict: 'A',
replace: true,
scope: {
students: "=",
collapsedTableRows: "="
},
templateUrl: 'partials/studentTable.html',
link: function(scope, elem, attrs) {
...
}
}
}
Template:
<table class="table">
<thead>
<tr>
<th><b>Name</b></th>
<th><b>Surname</b></th>
<th><b>Group</b></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="student in students track by $index">
<td>{{ student.name }}</td>
<td>{{ student.surname }}</td>
<td>{{ student.group }}</td>
</tr>
</tbody>
</table>
Use directive in my html like this:
<div student-table students="students"
collapsedTableRows="collapsedTableRows"></div>
And the parent controller:
.controller('SchoolController', ['$scope', 'User', function($scope, User){
$scope.students = [];
$scope.collapsedTableRows = [];
$scope.search = function(value) {
if(value) {
var orgId = $state.params.id;
var search = User.searchByOrg(orgId, value);
search.success(function (data) {
$scope.students = data;
$scope.collapsedTableRows = [];
_(data).forEach(function () {
$scope.collapsedTableRows.push(true);
});
});
}
}
}])
Now at the beginnig, the table is empty, because no users in students array. After I click search, and get list of students object, I put them to scope variable, but the directive does not update, neither it find change in model (scope.$watch('students',...). What am I missing?
P.S. If I simulate the data using $httpBackend, directive works as it should.
Please make sure that data object returning array of student because somtimes you have to use data.data that simple demo should helps you:
http://plnkr.co/edit/UMHfzD4oSCv27PnD6Y6v?p=preview
$http.get('studen.json').then(function(students) {
$scope.students = students.data; //<-students.data here
},
function(msg) {
console.log(msg)
})
You should try changing the controller this way
...
$scope.$apply(function() {
$scope.students = data;
})
...
This will start a digest loop, if it's not already in progress.
Another form that will do almost the same thing is this:
...
$scope.students = data;
$scope.$digest()
...
PS:
The first method is just a wrapper that execute a $rootScope.$digest() after evaluating the function, considering that a $digest evaluates the current scope and all it's children calling it on the $rootScope is pretty heavy.
So the second method should be preferred if it works.
Related
I've populated a table with data from a JSON file, and then when the user clicks on an item in the table, a container and it's fields display below and it SHOULD be populated with that specific data, though it isn't.
The function select is invoked through list-patents.htm which contains the table with a list of patents. I have used the $rootScope object so the function is accessible from multiple controllers i.e. patentDetailsCtrl
Why isn't my table in patent-item.htm being populated with the ng-repeat directive?
var app = angular.module('myApp', ['ngRoute', 'angularMoment', 'ui.router', "chart.js"]);
$stateProvider
.state("patents.list.item", {
url: "/patent-item",
templateUrl: "templates/patents/list/patent-item.htm",
params: {
id: null,
appNo: null,
clientRef: null,
costToRenew: null,
renewalDueDate: null,
basketStatus: null,
costBandEnd: null,
nextStage: null
},
controller: "patentDetailsCtrl"
})
app.run(function($rootScope) {
$rootScope.select = function() {
return $rootScope.patentItem = item;
}
});
app.controller('patentDetailsCtrl', ['$scope', '$http', function($scope, $http) {
$scope.selectedPatent = $scope.patentItem;
console.log($scope.selectedPatent);
}]);
list-patents.htm
<tbody>
<tr ng-repeat="x in patents">
<td ng-click="select(x)"><a ui-sref="patents.list.item({id: x.id, appNo: x.applicationNumber, clientRef: x.clientRef, costToRenew: x.costToRenew, renewalDueDate: x.renewalDueDate, basketStatus: x.basketStatus, costBandEnd: x.costBandEnd, nextStage: x.nextStage})">{{x.applicationNumber}}</a></td>
<td ng-bind="x.clientRef"></td>
<td ng-bind="x.costToRenew">$</td>
<td ng-bind="x.renewalDueDate"></td>
<td><button type="button" class="btn btn-danger" ng-click="remove(x.id)">Remove</button></td>
</tr>
</tbody>
patent-item.htm
<table>
<tbody>
<thead>
<tr>
<td>applicationNumber</td>
<td>clientRef</td>
<td>costToRenew</td>
<td>renewalDueDate</td>
<td>basketStatus</td>
<td>costBandEnd</td>
<td>nextStage</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="x in selectedPatent">
<td>{{x.applicationNumber}}</td>
<td>{{x.clientRef}}</td>
<td>{{x.costToRenew}}</td>
<td>{{x.renewalDueDate}}</td>
<td>{{x.basketStatus}}</td>
<td>{{x.costBandEnd}}</td>
<td>{{x.nextStage}}</td>
</tr>
</tbody>
</table>
</tbody>
You need to correct below points:
your $rootScope.select() method doesn't accept data (select(x)) passed from your list-patents.htm, So put item as param.,
Like : $rootScope.select = function(item) { \\ code
Sine you do ng-repeat on $rootScope.patentItem, then I guess you need to make it array, and push the passed data to it. (No need of return statement as well.)
So run block should be like :
app.run(function($rootScope) {
rootScope.patentItem = [];
$rootScope.select = function(item) {
$rootScope.patentItem.push(item);
}
});
See this Example Fiddle
To me it seems like you're trying to access the patentItem selected in your select function. If you're passing it to the $rootScope this way, you will also need to access it this way.
So change the line as follows...
$scope.selectedPatent = $rootScope.patentItem
i have some kind of legacy angularjs code which creates a dynamic table using a directive where the controller can overwrite the behavior of the table (on how to display the data)
It consists of the following setup (simplified):
Directive's controller
.directive('datatable', [function () {
return {
scope: {
items: '=',
tablemetadata: '=',
processors: '=?'
},
controller: ...
$scope.processField = function processField(item, data){
if($scope.processors === undefined){return;}
for(var i = 0; i < $scope.processors.length; i++){
if($scope.processors[i].field===field){
var newData = $scope.processors[i].processor(item, data);
return $sce.trustAsHtml(newData);
}
}
return data;
};
...
Directive's Template
<tr ng-repeat="item in items">
<td ng-repeat="column in tableMetadata.columns" ng-bind-html="processField(column.field, $eval('item.'+column.field))"></td>
</tr>
Controller
$scope.myItems = [{id: 2, otherProperty: "text"}];
$scope.tableMetadata = {
columns: [
{field: 'id', headerKey: 'object id'},
{field: 'otherProperty', headerKey: 'some data'},
]
};
$scope.tableProcessors = [
{field: 'id', processor: function(entry, data){ //data = content of object.id
var retVal = "<a ng-click='alert(" + data + ");'>click me</a>";
return retVal;
}}
];
Controller's view
<datatable items="myItems" tablemetadata="tableMetadata" processors="tableProcessors"></datatable>
I need to generate buttons (or other html-elements) for some specific properties, like a link (like shown above).
The Button is displayed but the ng-click handler is not working. This makes sense since it wasn't compiled to the scope.
How do I correctly compile the new element and add it to the table?
In your link method in the directive you have to use
elem.append( $compile(html)(scope) );
As for separating the concerns cleanly, I would make each <td> its own directive that inherits what you are currently concatenating as a string in its isolated scope properties. Instead of
var retVal = "<a ng-click='alert(" + data + ");'>click me</a>";
<tr ng-repeat="item in items">
<td ng-repeat="column in tableMetadata.columns" ng-bind-html="processField(column.field, $eval('item.'+column.field))"></td>
</tr>
use something like:
<tr ng-repeat="item in items">
<table-item ng-repeat="..." process-field="item"></table-item>
</tr>
/** directive compiles dynamically */
scope: {
processField: '='
},
link: function(scope, elem, attr, ctrl) {
var template = `<a ng-click="${ctrl.processField}"></a>`;
elem.append( $compile(template)(scope) );
}
A simple solution can be to not use an isolated scope.
Change your scope from scope: { ... } to scope: true and use $scope.$eval to evaluate your attributes.
Another solution (most elegant) can be to use angularjs transclusion (see here). But this solution ask to modify your dom representation of your directive.
I want to implement 'edit' feature to any book, but I can't get my book.
How it works now:
I click on the any record (which is <tr>).
I am being redirected to the books_edit state
This 'edit' page must have all the data in form of current book (but it doesn't).
So, the question is: How can I pass book from the books state to books_edit state and submit it correctly?
HTML piece:
<tr ng-click="bookCtrl.editBook(book)" ng-repeat="book in bookCtrl.books">
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.price }}</td>
<td>{{ book.pubdate | date }}</td>
<td>{{ book.coverUrl }}</td>
<td>{{ book.pagesCount}}</td>
</tr>
States:
.state('books_new', {
url: '/books/new',
templateUrl: 'books/book_new.html',
controller: 'BookCtrl as bookCtrl'
})
.state('books_edit', {
url: '/books/edit',
templateUrl: 'books/book_edit.html',
controller: 'BookCtrl as bookCtrl'
})
.state('books', {
url: '/books',
templateUrl: 'books/books.html',
controller: 'BookCtrl as bookCtrl'
})
Controller's methods:
editBook: function(book) {
if (book) {
console.log(book); // logs correct book
$state.go('books_edit'); // tried to send `book` as a parameter, didn't work
}
},
submitBook: function(book) {
if (book) {
console.log(book);
return books.$save(book).then(function(data) {
$state.go('books');
});
}
}
Edit snippet:
<form class="container col-lg-3" ng-submit="bookCtrl.submitBook(book)">
<div class="input-group">
<label class="col-sm-2 control-label">Назва:</label>
<input type="text" ng-model="book.name" class="form-control">
I've tried to send book as a parameter in state, but no result.
The best way to handle this, is to be 'stateless'. This way a user can bookmark the edit page, and reload the page without requiring any state to be present in the app.
Pass the id of the book you want to edit as a url parameter to the edit state, like so:
state config:
.state('books_edit', {
url: '/books/edit/:bookId',
templateUrl: 'books/book_edit.html',
controller: 'BookCtrl as bookCtrl'
})
controller:
$state.go('books_edit', {bookId: book.id});
In the edit controller, fetch the book using the id from the url, using the $stateParams service:
angular.module('myapp').controller('BookCtrl', function($scope, $stateParams){
//fetch the book id from the url params
var bookId = $stateParams.bookId;
//now get the book with the given id
});
I would advise to use a separate controller for the edit functionality, i.e. do not use 'BookCtrl' for every view.
Define state parameters as following
$stateProvider.state('books_edit', {url: '/books/:bookId',params: {obj: null},templateUrl: 'books/books_edit.html',controller: 'booksCtrl'})
when calling pass parameter like this:
$state.go('books_edit',{obj: myobj});
In controller you can receive parameter using
$state.params.obj
Hope it helps.
You can use a service to reach this. Create a service where you can set/get the value and inject in both controllers. The service looks like this:
app.service('bookService', function() {
var books = [];
var addBook = function(obj) {
books.push(newObj);
};
var getBook = function(){
return books;
};
return {
addBook: addBook,
getBook: getBook
};
});
And, in controller:
editBook: function(book) {
if (book) {
// ensure to inject productService in controller
bookService.addBook(book)
console.log(book); // logs correct book
$state.go('books_edit'); // tried to send `book` as a parameter, didn't work
}
},
In book_edit controller:
.....
// ensure to inject productService in controller
$scope.book = bookService.getBook(book)
....
You can also use $broadcast, read more:
On and broadcast in angular
Hope it helps
Try passing it in state.go as something like this "books/" and then use state params to retrieve it.
state('books_edit', {
url: '/books/edit:bookID',
templateUrl: 'books/book_edit.html',
controller: 'BookCtrl as bookCtrl'
})
submitBook: function(bookID) {
if (bookID) {
console.log(bookID);
return books.$save(bookID).then(function(data) {
$state.go('books/'+<bookID>);
});
}
}
in the Controller
editBook: function($scope, $stateParams) {
$scope.bookID = $stateParams.bookID;
}
Thanks #fikkatra and #Gurpinder for helping with this! The complete solution is following:
Add this to the books_edit state:
params: {data: null}
In the editBook() function send parameters to the next state:
$state.go('books_edit',{bookId: book.$id, data: book});
Add this to the bookCtrl - bookCtrl.currentBook = $state.params.data;
Change ng-model in the view to bookCtrl.currentBook.KEY_NAME
I have a JSON object that does not have proper data. So i want to replace the obtained value with one from a lookup table, but I'm not sure how to replace the value of the data corresponding to the lookup table.
lookupTable = {
"pizza": function() {
console.log("food");
},
"house": function() {
console.log("building");
},
"air": function() {
console.log("nothing");
}
};
$scope.value =lookupTable["pizza"]()
my html file has
<tr ng-repeat="x in names">
<td>{{ x.lookupTable["pizza"]() }}</td>
My code is at http://plnkr.co/edit/w4lOFVRo9vSi8vqfbpXV?p=preview
Any help is appreciated!
Here are some problems in your code from the link you provided:
Functions on the lookupTable is not returning anything as pointed out in the previous answers.
lookupTable is not a property of $scope.names so using x.lookupTable is invalid.
To make it work, you should:
The functions from lookupTable should return the actual values instead of using console.log
Bind lookupTable to $scope
Use lookupTable directly inside the view as it is bound to $scope
Here is the relevant code:
<div ng-app="myApp" ng-controller="customersCtrl">
<table>
<tr ng-repeat="x in names">
<td>{{ lookupTable[x.Name]() }}</td>
</tr>
</table>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $http) {
$http.get("data.json")
.then(function(response) {
$scope.names = response.data.records;
});
$scope.lookupTable = {
"pizza": function() {
return 'food';
},
"house": function() {
return 'building';
},
"air": function() {
return "nothing";
}
};
});
</script>
Your code is very confused. I tried to edit your plunker, maybe is easier to see that to explain.
<!DOCTYPE html>
<html>
<scriptsrc="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="customersCtrl">
<table>
<tr ng-repeat="x in names">
<td>{{ lookupTable[x.Name] }}</td>
</tr>
</table>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $http) {
$http.get("data.json")
.then(function(response) {
$scope.names = response.data.records;
});
lookupTable = {
"pizza": "food",
"house": "building",
"air": "nothing"
};
$scope.lookupTable = lookupTable;
$scope.value = lookupTable["pizza"]
console.log(lookupTable["house"])
});
</script>
Looking at your code, I am not sure what you want to achieve.
The ng-repeat gives you three objects out of names, but none of them has a lookupTable member, so {{ x.lookupTable["pizza"] }} fails silently.
You can make it visible, if you just bind to {{ x }}
I'm using BootGrid Data table and JSON file as my data source for the table.
Service
.service('datatableService', ['$resource', function($resource){
this.getDatatable = function(id, email, date) {
var datatableList = $resource("data/data-table.json");
return datatableList.get ({
id: id,
email: email,
date: date
})
}
}])
Controller
.controller('datatableCtrl', function($scope, datatableService){
//Get Data Table Data
$scope.id = datatableService.id;
$scope.email = datatableService.email;
$scope.date = datatableService.date;
$scope.dtResult = datatableService.getDatatable($scope.id, $scope.email, $scope.date);
})
Directive
.directive('bootgrid', ['$timeout', function($timeout){
return {
restrict: 'A',
link: function(scope, element, attr){
$('#data-table-basic').bootgrid({
css: {
icon: 'md icon',
iconColumns: 'md-view-module',
iconDown: 'md-expand-more',
iconRefresh: 'md-refresh',
iconUp: 'md-expand-less'
}
});
}
}
}])
HTML
<div class="table-responsive" data-ng-controller="datatableCtrl">
<table id="data-table-basic" class="table table-striped" data-bootgrid>
<thead>
<tr>
<th data-column-id="id" data-type="numeric">ID</th>
<th data-column-id="sender">Sender</th>
<th data-column-id="received" data-order="desc">Received</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="w in dtResult.list">
<td>{{ w.id }}</td>
<td>{{ w.email }}</td>
<td>{{ w.date }}</td>
</tr>
</tbody>
</table>
</div>
When I run this, I'm getting no data inside <tbody> but when I remove the directive, I can see all the JSON data are rendered inside the table. I want both ng-repeat and and directive works together. I tried to set the priority in directive as,
...
return {
restrict: 'A',
priority: 1001,
...
But no luck. http://plnkr.co/edit/rWCVXTjxOGZ49CeyIn9d?p=preview
Please help me fix this. I would appropriate if you could fix the above pen.
Regards
Priority setting will not help here because it is used to regulate order of compilation of directives defined on the same element.
You can delay bootgrid directive initialization until very next digest cycle using $timeout service. You will also need to watch for data object changes since you are loading it with AJAX.
app.directive('bootgrid', function($timeout) {
return {
link: function(scope, element, attr) {
scope.$watch(attr.bootgrid + '.length', function(newVal, oldVal) {
if (newVal !== oldVal && newVal) {
$timeout(function() {
element.bootgrid({
css: {
icon: 'md icon',
iconColumns: 'md-view-module',
iconDown: 'md-expand-more',
iconRefresh: 'md-refresh',
iconUp: 'md-expand-less'
}
});
});
}
});
}
}
});
Demo: http://plnkr.co/edit/ehbzoFGOqhQedchKv0ls?p=preview