Angularjs: Why is $scope.variable in $watch not up-to-date? - angularjs

I created a Plunker here to illustrate the problem. Here is the main code:
<body ng-controller="MainCtrl">
<table class="table table-bordered table-striped table-condensed table-hover">
<caption class="text-left" style="background-color: lightgray">
Search: <input ng-model="searchString" style="width: 80px" />
Filted count: {{filteredList.length}}
</caption>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Sex</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="emp in filteredList = (data.employees | filter:searchString)">
<td>{{emp.firstName}}</td>
<td>{{emp.lastName}}</td>
<td>{{emp.sex}}</td>
</tr>
</tbody>
</table>
</body>
App.js:
$scope.searchString = '';
$scope.$watch('searchString', function(str) {
console.log($scope.filteredList.length);
});
The problem is that the console.log of filteredList.length is always one loop behind (compared to html which is correct), that is, from previous filter. How can I fix it?

You are logging filteredList.length as soon as searchString changes. But, at at moment, filteredList has not yet been modified by the filter in ngRepeat.
One way to fix this would be to $watch filteredList.length instead:
$scope.$watch(function() {
return ($scope.filteredList || []).length;
...
Update
I really like shaunhusain's comment. His suggestion is probably closer to what you really want to do.
$scope.searchString = '';
// Initialize filteredItems as a copy of data.employees
$scope.filteredItems = angular.copy($scope.data.employees);
// Watch your search string instead and apply filtering using $filter
$scope.$watch(function() {
return $scope.searchString;
},
function(str) {
$scope.filteredItems = $filter('filter')($scope.data.employees, {$: $scope.searchString}, false);
console.log($scope.filteredItems.length);
});
Then, your ngRepeat is simply:
<tr ng-repeat="emp in filteredItems">
<td>{{emp.firstName}}</td>
<td>{{emp.lastName}}</td>
<td>{{emp.sex}}</td>
</tr>

Related

How to collapse and hide in ng-repeat over table?

So I have a table where I need to show parent deals and on ng-show(clicking parent deal) I want to show child deals.
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">Name</th>
<th scope="col">No_Of_Orders</th>
<th scope="col">Size</th>
<th scope="col">Book_Size</th>
<th scope="col">Remarks</th>
<th scope="col">Price_Guidance</th>
<th scope="col">CONTROL</th>
<th scope="col">Status</th>
<th scope="col">Current_Cut_Off</th>
<th scope="col">Tenor</th>
<th scope="col">Book</th>
<th scope="col">ORDCOLUMN</th>
<th scope="col">PKEY</th>
<th scope="col">Save</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="parentDeal in parentDeals track by $index" ng-click="childShow(parentDeal.PKEY)">
<td>{{parentDeal.Name}}</td>
<td>{{parentDeal.No_Of_Orders}}</td>
<td>{{parentDeal.Size}}</td>
<td>{{parentDeal.Book_Size}}</td>
<td>{{parentDeal.Remarks}}</td>
<td>{{parentDeal.Price_Guidance}}</td>
<td>{{parentDeal.CONTROL}}</td>
<td>{{parentDeal.Status}}</td>
<td>{{parentDeal.Current_Cut_Off}}</td>
<td>{{parentDeal.Tenor}}</td>
<td>{{parentDeal.Book}}</td>
<td>{{parentDeal.ORDCOLUMN}}</td>
<td>{{parentDeal.PKEY}}</td>
<td>{{parentDeal.Save}}</td>
<tr ng-repeat="childDeal in childDeals track by $index" ng-show = "childRowShow_{{childDeal.PKEY}}">
<td>{{childDeal.Name}}</td>
<td>{{childDeal.No_Of_Orders}}</td>
<td>{{childDeal.Size}}</td>
<td>{{childDeal.Book_Size}}</td>
<td>{{childDeal.Remarks}}</td>
<td>{{childDeal.Price_Guidance}}</td>
<td>{{childDeal.CONTROL}}</td>
<td>{{childDeal.Status}}</td>
<td>{{childDeal.Current_Cut_Off}}</td>
<td>{{childDeal.Tenor}}</td>
<td>{{childDeal.Book}}</td>
<td>{{childDeal.ORDCOLUMN}}</td>
<td>{{childDeal.PKEY}}</td>
<td>{{childDeal.Save}}</td>
</tr>
</tr>
</tbody>
</table>
This is the basic data:-
$scope.allDeals = [
{
"ORDCOLUMN":"2017-11-17T05:00:00.000000000Z",
"CONTROL":"N",
"PKEY":"UD0000000000720",
"Name":"rajat10 10:19",
"Tenor":"0Y",
"Size":0,
"Price_Guidance":"43%",
"Remarks":"-",
"Book_Size":20,
"Status":"Open",
"No_Of_Orders":2,
"Current_Cut_Off":2.3,
"Book":"Book#ReOffer",
"Save":"Edit"
},
{
"ORDCOLUMN":"2017-11-17T05:00:00.000000000Z",
"CONTROL":"Y",
"PKEY":"UD0000000000720",
"Name":"rajat10 10:19",
"Tenor":"Multi ...",
"Size":0,
"Price_Guidance":"-",
"Remarks":"-",
"Book_Size":20,
"Status":" Open",
"No_Of_Orders":2,
"Current_Cut_Off":2.3,
"Book":"Book#ReOffer",
"Save":"Edit"
},
{
"ORDCOLUMN":"2017-11-17T05:00:00.000000000Z",
"CONTROL":"N",
"PKEY":"UD0000000000720",
"Name":"rajat10 10:19",
"Tenor":"Perp",
"Size":0,
"Price_Guidance":"19%",
"Remarks":"-",
"Book_Size":0,
"Status":"Open",
"No_Of_Orders":2,
"Current_Cut_Off":2.3,
"Book":"Book#ReOffer",
"Save":"Edit"
}
....and so on
]
So, this data can be grouped by PKEY and parent object has control - "Y", child object has control - "N". Note:- There can be objects where there is only parent deal with no child deals.
In my app.js:-
$scope.parentDeals = $scope.allDeals.filter(function (item,index) {
return item.CONTROL == "Y";
});
$scope.childDeals = [];
$scope.childShow = function (PKEY) {
if($scope["childRowShow_"+PKEY]){
$scope["childRowShow_"+PKEY] = false;
}
else{
$scope.childDeals = $scope.allDeals.filter(function (item,index) {
if(item.PKEY == PKEY && item.CONTROL == "N"){
return item;
}
});
$scope["childRowShow_"+PKEY] = true;
}
};
Here, I am making two arrays:- parentDeals and childDeals.
Right now, I am applying ng-repeat on the separate row so, child deals are showing after all parent deals. Is there any way I can show them below the respective parent deals or can apply a different solution?
Yes, with ng-repeat-start and ng-repeat-end, we can explicitly specify the starting and ending for ng-repeat. So, instead of using the ng-repeat directive on one single DOM element, we can specify the ng-repeat-start and ng-repeat-end on any two DOM elements.
For eg.
<div ng-app="app">
<table ng-controller="TestController">
<tr ng-repeat-start="d in data">
<td><strong>{{d.name}}</strong></td>
</tr>
<tr ng-repeat-end>
<td>{{ d.info }}</td>
</tr>
</table>
</div>
<script>
var app = angular.module('app', []);
app.controller('TestController', function ($scope) {
$scope.data = [{
name: 'ABC',
info: 'Bangalore'
}, {
name: 'XYZ',
info: 'Mumbai'
}];
});
</script>

I just want to load a particular div on some actions in angular js

I just want to load a particular div on some actions in angular js.
<div class="row">
<div class="col-md-12">
<div id = "div_1" class="table-responsive" ng-controller="usercontroller" ng-init="displayData() ">
<table class="table table-hover table-bordered">
<thead align="center">
<tr>
<th>Service Request Number</th>
<th>Name of service request</th>
<th>Date of request</th>
<th>Closure date</th>
<th>Current state</th>
<th>Current owner</th>
<th>Link to share point folder</th>
<th>Schedule variance</th>
<th align="center">Details</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="x in service track by $index">
<td><span ng-bind="x.id"></span></td>
<td><span ng-bind="x.sales_force_id"></span></td>
<td><span ng-bind="x.submission_date"></span></td>
<td><span ng-bind="x.closure_deadline"></span></td>
<td><span ng-bind="x.item_status"></span></td>
<td></td>
<td></td>
<td></td>
$scope.displayData = function()
{
alert($scope.firstRole);
alert("nishant singh");
if(empIVal == 1 && $scope.firstRole == 'Initiator' ){
alert("i am a initiator");
$http.get("commonGet.jsp?sqlStr=select * from service_request.service_request_data where emp_id="+empHidVal+ " order by id desc")
.then(function(response){
$scope.service = response.data;
});
}
Here I want to load the div again when the displayFunction() is called and response is been returned. Also, the response is JSON object. I am able to call the function using ng-change directive and also able to get the desired JSON object, but I don't know how to load the div again after the response.
wrap your assignment to service variable in timeout so that angular is aware of the changes
$timeout(function(){
$scope.service = response.data;
});
Thus your whole method becomes something like this.
$http.get("commonGet.jsp?sqlStr=select * from service_request.service_request_data where emp_id="+empHidVal+ " order by id desc")
.then(function(response){
$timeout(function(){
$scope.service = response.data;
});
});
}

How to populate a table with AngularJS

I have a table:
<h4>Table of Results</h4>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col">Sub-Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="object in results>
<td>{{object.field1}}</td>
<td>{{object.field2}}</td>
<td>{{object.field3}}</td>
</tr>
</tbody>
</table>
And I have a controller:
angular.module('app', [])
.controller("Ctrl",['$scope', function ($scope) {
$scope.BtnIndex;
$scope.results = [];
$scope.selectBtn = function (index, model1, model2) {
if (index == $scope.BtnIndex)
$scope.BtnIndex = -1;
else {
$scope.newItem = {
field1 : model2.name,
field2 : model2.val,
field3 : model1.name
}
$scope.results.push($scope.newItem);
}
};
I can't work out why the table is not populating with the data. I have checked the console and it is showing the data, as I expected, but it just isn't populating the table.
I'm expecting the answer to be right in front of me, but I can't see it.
You are not binding the results variable to the scope
Please change
results = [];
to this
$scope.results = [];
Here's the official information from Angular themselves
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
This works - Plunker
JS
$scope.results = [];
$scope.selectBtn = function (index, model1, model2) {
if (index == $scope.BtnIndex)
$scope.BtnIndex = -1;
else {
$scope.newItem = {
field1 : index,
field2 : model1,
field3 : model2
}
$scope.results.push($scope.newItem);
}
}
Markup
<body ng-controller="MainCtrl">
<button ng-click='selectBtn("hello", "world", "today")'>Press me</button>
<h4>Table of Results</h4>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col">Sub-Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="object in results">
<td>{{object.field1}}</td>
<td>{{object.field2}}</td>
<td>{{object.field3}}</td>
</tr>
</tbody>
</table>
</body>
It's not possible to see how you are calling $scope.selectBtn in your markup so I've created a simple example from your question.

angular angular-table dynamic header name

I use angular and angular-table to display multiple tables on the same page.
I need to create dynamic table with dynamic header and dynamic content.
Plunkr her
This is a working example with non dynamic header but I don't find how to make dynamic
The controller :
angular.module('plunker', ['ui.bootstrap',"angular-table","angular-tabs"]);
function ListCtrl($scope, $dialog) {
$scope.cols= ['index','name','email'];
$scope.list = [
{ index: 1, name: "Kristin Hill", email: "kristin#hill.com" },
{ index: 2, name: "Valerie Francis", email: "valerie#francis.com" },
...
];
$scope.config = {
itemsPerPage: 5,
fillLastPage: true
};
}
HTML
<!-- this work -->
<table class="table table-striped" at-table at-paginated at-list="list" at-config="config">
<thead></thead>
<tbody>
<tr>
<td at-implicit at-sortable at-attribute="name"></td>
<td at-implicit at-sortable at-attribute="name"></td>
<td at-implicit at-sortable at-attribute="email"></td>
</tr>
</tbody>
</table>
<!-- this fail ... -->
<table class="table table-striped" at-table at-paginated at-list="list" at-config="config">
<thead></thead>
<tbody>
<tr>
<td ng-repeat='col in cols' at-implicit at-sortable at-attribute="{{col}}"></td>
</tr>
</tbody>
</table>
I am missing some think or it is not possible with this module ?
Did you know another module where you can have dynamic header and pagination ? ( i try also ngTable but have some bug issu with data not being displayed )
Through the below code, you can generate dynamic header
<table class="table table-hover table-striped">
<tbody>
<tr class="accordion-toggle tblHeader">
<th ng-repeat="(key, val) in columns">{{key}}</th>
</tr>
<tr ng-repeat="row in rows">
<td ng-if="!$last" ng-repeat="col in key(row)" ng-init="val=row[col]">
{{val}}
</td>
</tr>
</tbody>
</table>
Angular Script
var app = angular.module('myApp', []);
app.controller('myControl', function ($scope, $http) {
$http.get('http://jsonplaceholder.typicode.com/todos').success(function (data) {
$scope.columns = data[0];
$scope.rows = data;
}).error(function (data, status) {
});
$scope.key = function (obj) {
if (!obj) return [];
return Object.keys(obj);
}
});

Dynamically setting new ng-model when pushing model to array

Can't figure out how to dynamically add a new model whenever a new row is added to the page. For example, the input select box ng-model= infos.rigBonusInfo.rigName is used for all select box I've added to the page. I would like to have a different model attached to a each select inputs. I tried using ng-model= infos.rigBonusInfo.rigName[rigBonus] but it doesn't work for the rates as the same model gets attachedto each rate field.
Pretty much what I want to do is to bind a new model whenever a new row gets pushed into the array.
Currently, I have a nested table which is the following:
<div class="col-lg-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Rig</th>
<th>Rig Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rig in rigs">
<td>{{ $index + 1 }}</td>
<td>{{ rig.name }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-2"></div>
<div class="col-lg-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Bonus Name</th>
<th>Rate</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="bonus in create.rigBonusRates">
<td>{{ bonus.rateName }}</td>
<td>{{ bonus.rate }}</td>
</tr>
</tbody>
</table>
</div>
<table>
<thead>
<tr>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rigDate in rigDateList track by $index">
<td><input ui-date="datepickerOptions" ng-model="date" /></td>
<td>
<table>
<thead>
<tr>
<th>Rig</th>
<th>Rate1</th>
<th></th>
<th>Rate2</th>
<th></th>
<th>Rate3</th>
<th></th>
<th>Rate4</th>
<th></th>
<th>Comments</th>
</tr>
</thead>
<tr ng-repeat="rigBonus in rigBonusList track by $index">
<td><select ng-options="rig as rigs.indexOf(rig) + 1 for rig in rigs" ng-model="infos.rigBonusInfo.rigName[rigBonus]" ></select></td>
#for (var i = 1; i < 5; i++)
{
<td><select ng-options="rigBonus.rateName for rigBonus in create.rigBonusRates" ng-model="infos.rigBonusInfo.rate#(#i)"></select></td>
<td><input type="text" ng-disabled="infos.rigBonusInfo.rate#(#i).rateName != 'Special' " ng-model=infos.rigBonusInfo.rate#(#i).rate /></td>
}
<td><input ng-model="info.rigBonusInfo.comments" /></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<div>
<button type="button" ng-click="add()">Add</button>
<button type="button" ng-click="addDate()">Add Date</button>
</div>
My current controller has the following:
angular.module('RigBonus').controller('rigCreateController', ['$scope', '$http', 'PayPeriodService', 'RigLegendService',
function ($scope, $http, PayPeriodService, RigLegendService, RigBonusRateService) {
$scope.rigs = RigLegendService.getRigLegend();
$scope.datepickerOptions = {
orientation: 'top',
startDate: PayPeriodService.getStartDate(),
endDate: PayPeriodService.getEndDate()
};
$http({ method: 'GET', url: '/Home/CreateData' }).success(function (data) {
$scope.create = data;
$scope.infos = {
rigBonusInfo: {
rigName: $scope.rigs[0],
rate1: $scope.create.rigBonusRates[0],
rate2: $scope.create.rigBonusRates[0],
rate3: $scope.create.rigBonusRates[0],
rate4: $scope.create.rigBonusRates[0],
comment: ""
}
};
$scope.add = function () {
$scope.rigBonusList.push();
};
$scope.addDate = function(){
$scope.rigDateList.push("");
};
});
$scope.rigBonusList = [$scope.rigBonusInfo];
$scope.rigDateList = [];
$scope.submit = function () {
$http.post('/Home/Create', {model: "testing"} );
};
}]);
I figured out my issue. My problem was that I was not sure how to generate a new object when a new row of controls are added. Think I should have put something on fiddleJS to help people visualize it better. As a static model was used ($scope.infos) as ng-model, the same model was used for two different controls which I don't want. I want all my controls to be unique.
The fix was to create the object I had in mind which is the following:
$scope.rigDateList = [{
date: "",
rigBonusList: [{}]
}];
So it is an array of objects where the object contains a date and another array of objects.
When I want to push new objects to the inside array which I didn't know I could just create objects like this at the time. I was trying to figure out a way to dynamically create new models ng-model could by declaring them in the controller. I use the following function:
$scope.rigDateList[$scope.rigListIndex].rigBonusList.push({
rigName: "",
rate1: "",
rate2: "",
rate3: "",
comments: ""
});
I also didn't know that I could use elements inside the array from ng-repeat. In the following case, it is rigBonus that I could have used as a model instead of infos model.
<tr ng-repeat="rigBonus in rigDate.rigBonusList track by $index">
<td><select ng-options="rig as rigs.indexOf(rig) + 1 for rig in rigs" ng-model="rigBonus.rigName"></select></td>
and when I want to push to the outside array I use the following:
$scope.rigDateList.push({
date: "",
rigBonusList: [""]
});
$scope.rigListIndex = $scope.rigListIndex + 1;
I use an index to keep track of which object I'm in.
A more closest question and answer is that:
Ng-repeat with dynamic ng-model on input not working
please, take a look.

Resources