Scoping issue on ng-change function on HTML SELECT in AngularJS - angularjs

I am trying to implement, in a larger context, exactly what is being done in FIDDLE, shown here:
HTML:
<div ng-controller="MyCtrl">
<select
ng-options="ptoGroup as ptoGroup.classname for ptoGroup in ptoGroupTypes"
ng-model="ptoItem"
ng-change="ptoUpdateUserQueryForm1()">
</select>
{{ptoItem.classname}}
</div>
Javascript
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
myApp.controller("MyCtrl", function($scope) {
$scope.ptoGroupTypes = [
{"classname": 'n1'},
{"classname": 'n2'},
{"classname": 'n3'}
];
$scope.ptoUpdateUserQueryForm1 = function() {
console.log("1. new formtype = " + $scope.ptoItem.classname);
};
});
This jsfiddle works great.
In my application, everything works exactly the same way except that inside the $scope.ptoUpdateUserQueryForm function, the value of $scope seems okay but the value of $scope.ptoItem is undefined.
My HTML
<body ng-app="cdu_app">
<div ng-controller="cdu_controller">
<table>
<tr>
<td>Type</td>
<td>
<select
ng-options="ptoGroup as ptoGroup.classname for ptoGroup in ptoGroupTypes"
ng-model="ptoItem"
ng-change="ptoUpdateUserQueryForm()"></select>
</td>
</tr>
<tr>
<td>ptoItem.classname: </td>
<td>{{ ptoItem.classname }}</td>
</tr>
</table>
</div>
</body>
My Javascript
var cduApp = angular.module("cdu_app", []);
cduApp.controller('cdu_controller', function ($scope, $http) {
$scope.ptoGroupTypes = [
{ "classname": "Team" },
{ "classname": "Organization" }
];
$scope.ptoUpdateUserQueryForm = function() {
console.log(" $scope.ptoUpdateUserQueryForm = function()");
console.log("new form type is: " + $scope.ptoItem.classname);
};
});
I am running with Angularjs 1.4.8.

Not sure if that was a type but the object ptoGroupTypes needs to be as
$scope.ptoGroupTypes = [
{ "classname": "Team" },
{ "classname": "Organization" }
];
The closing '}' should be ']'.
Checkout this FIDDLE. It is working.
Just thought of mentioning I changed the name of app from cdu_app to myApp for the sake of Fiddle to work.

Related

Binding scope object to factory object

I am connecting to a websocket feed in a factory, which gives me real time bitcoin price data.
I define service.prices as an object in websocketFactory, and set wsvm.prices = websocketFactory.prices in the controller.
The wsvm.prices.btc property is not updating in the view, but is logging correctly in console.
code:
factory
app.factory('websocketFactory', ['$http', function($http) {
var service = {}
service.prices = {
"btc": 0
}
service.gdax = function() {
const socket = new WebSocket('wss://ws-feed.gdax.com')
var message = {
"type": "subscribe",
"channels": [{
"name": "ticker",
"product_ids": [
"BTC-USD"
]
}, ]
}
socket.addEventListener('open', function(event) {
socket.send(JSON.stringify(message));
})
// Listen for messages
socket.addEventListener('message', function(event) {
var dataObj = JSON.parse(event.data)
if (dataObj.price) {
console.log(dataObj.price) //logging real time data
service.prices.btc = dataObj.price //this should be updating the view
}
});
}
return service
}])
controller
app.controller('WebsocketController', WebsocketController)
WebsocketController.$inject = ['$scope', 'websocketFactory']
function WebsocketController($scope, websocketFactory) {
var wsvm = this
wsvm.prices = websocketFactory.prices
websocketFactory.gdax()
}
view
<div ng-controller="PortfolioController as vm">
<div class="row">
<div class="col-sm-6">
<h2 style="text-align: center;">Account Balances</h2>
</div>
<div class="col-sm-6">
<table class="table">
<thead>
<tr>
<td>24h Change</td>
<td>Total Val USD</td>
<td>BTC Price</td>
</tr>
</thead>
<tbody>
<tr>
<td ng-class="{change_loss: vm.totals.change_24h_usd < 0, change_win: vm.totals.change_24h_usd > 0}"><b>{{vm.totals.change_24h_usd | currency}}</b></td>
<td><b>{{vm.totals.total_usd | currency}}</b></td>
<td ng-controller="WebsocketController as wsvm">{{wsvm.prices.btc}}</td>
</tr>
</tbody>
</table>
</div>
</div>
How to correctly bind factory to controller for real time data?
Changing scope in the socket listener is outside of angular context. Whenever that happens angular needs to be notified to run a digest to update view
Try the following:
app.factory('websocketFactory', ['$http', '$rootScope', function($http, $rootScope) {
......
socket.addEventListener('message', function(event) {
var dataObj = JSON.parse(event.data)
if (dataObj.price) {
console.log(dataObj.price) //logging real time data
$rootScope.$apply(function(){
service.prices.btc = dataObj.price //this should be updating the view
});
}
});
});
I also suggest you look for an angular socket module that will take care of all of this for you internally

Angular Delete row from table using angular services

I am trying to delete a table row using angular services, but unfortunately I don't know how to do that. I have to do that using services because I am using several services with the same control.
<script>
var myApp = angular.module("myApp", []);
myApp.service('allCurrentSettingsService', ['$http', '$q', function ($http, $q) {
var allSettings = null;
this.getList = function () {
var def = $q.defer()
if (allSettings) {
def.resolve(allSettings);
} else {
$http.post('GetAllCurrentSettings')
.then(function (response) {
var response = $.parseJSON(response.data)
allSettings = response;
def.resolve(allSettings);
});
}
return def.promise;
}
}]);
myApp.controller('myController', ['$scope', 'allCurrentSettingsService',
function ($scope, allCurrentSettingsService) {
$scope.allSettings = '';
allCurrentSettingsService.getList().then(function (value) {
$scope.allSettings = value;
});
}
]);
</script>'
And this is my HTML:
`
<div ng-controller="myController">
<table border="1">
<tr ng-repeat="setting in allSettings">
<td><input id="Button1" type="button" value="Delete" data-ng-click="DeleteRow(rowValue)" /></td>
<td class="hidden">{{setting.SettingID}}</td>
<td>{{setting.CompanyName}}</td>
<td>{{setting.CustomerName}}</td>
<td>{{setting.DocumentName}}</td>
</tr>
</table>
</div>
`
Delete method from controller:
[HttpPost]
public static void DeleteRecord(int settingID)
{
try
{
using (SqlConnection conn = new SqlConnection(connStringApps))
{
conn.Open();
using (SqlCommand command = new SqlCommand("DeleteCurrentRecord", conn))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add("#SettingId", SqlDbType.VarChar).Value = settingID;
command.ExecuteNonQuery();
command.Parameters.Clear();
}
conn.Close();
}
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
Answer Updated
I do not recommend to remove a row from your service, it's better if you do that from controller, but for figure out how can remove a row from service see the example.
To Remove From Controller you just convert the service codes as controller like what you see in sample.
var app = angular.module("app", []);
app.controller("ctrl", function ($scope, service) {
$scope.data = [
{ name: "a" },
{ name: "b" }
];
$scope.deleteRow = function (row) {
$scope.data = service.removeRow(row, $scope.data);
}
$scope.deleteFromController = function (row) {
var indexOf = $scope.data.indexOf(row);
$scope.data.splice(indexOf, 1);
}
});
app.service("service", function ($rootScope) {
this.removeRow = function (row, data) {
var indexOf = data.indexOf(row);
data.splice(indexOf, 1);
return data;
}
});
<!DOCTYPE html>
<html ng-app="app" ng-controller="ctrl">
<head>
<title></title>
</head>
<body>
<h5>click on rows: delete from service</h5>
<table>
<tr ng-repeat="row in data" ng-click="deleteRow(row)">
<td>{{row.name}}</td>
</tr>
</table>
<h5>click on rows: delete from controller</h5>
<table>
<tr ng-repeat="row in data" ng-click="deleteFromController(row)">
<td>{{row.name}}</td>
</tr>
</table>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</body>
</html>
From the front end if you want to remove get the index of setting which you want to remove and then use splice.
in the html there has to be an button which initiate the delete operation
your html should be, a rough idea
<tr ng-repeat ="setting in allSettings">
<td>{{setting.SettingID}}</td>
<!-- other items which you wnted to display-->
<!-- put one button for delete and use $index to track the index of item to be removed-->
<td><button ng-click="removeRow($index)">Remove Row</button></td>
</tr>
your controller>>
$scope.removeRoe = function(index){
//splice will remove the setting row and it will get reflected n the view
$scope.allSettings.splice(index,1);
}
FYI
You need not use service, the removing part has to be done in controller. If you need to hit a backend service(post or delete request) that removes the setting then you need to use angular service

Angularjs Service does not work

I define a Service to share a variable between two controllers, but when i set the variable in a controller and then get this from another controller it does not get the correct value , this is the service:
App.service("ProductService", function () {
var productTotalCount = {};
return {
getproductTotalCount: function () {
return productTotalCount;
},
setproductTotalCount: function (value) {
productTotalCount = value;
}
}
});
and this is the controller which i set productTotalCount:
App.controller("ProductController", function ($scope, $http, $rootScope, ProductService) {
$scope.GetAllProducts = $http.get("GetAllProductsInformation").success(function (data) {
$rootScope.Products = data.Data;
ProductService.setproductTotalCount(data.TotalCount); // i set productTotalCount here and it's value became 19
});
$scope.editProduct = function (data) {
$scope.model = data;
$rootScope.$broadcast('modalFire', data)
}
});
and when i get the productTotalCount in this controller it return object instead of 19 :
App.controller('Pagination', function ($scope, ProductService) {
debugger;
$scope.totalItems = ProductService.getproductTotalCount(); // it should return 19 but return object!!
$scope.currentPage = 1;
$scope.itemPerPage = 8;
});
what is the problem?
EDIT: this is the html, it may help :
<div ng-controller="ProductController" ng-init="GetAllProducts()">
<div class="row" style="margin-top:90px" ng-show="!ShowGrid">
<article class="widget">
<header class="widget__header">
<div class="widget__title">
<i class="pe-7s-menu"></i><h3>ProductList</h3>
</div>
<div class="widget__config">
<i class="pe-7f-refresh"></i>
<i class="pe-7s-close"></i>
</div>
</header>
<div class="widget__content table-responsive">
<table class="table table-striped media-table">
<thead style="background-color:rgba(33, 25, 36,0.1)">
<tr>
<th style="width:40%">edit</th>
<th style="width:30%">Price</th>
<th style="width:30%">ProductName</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="product in Products">
#*<td>{{product.ProductDescription}}</td>*#
<td>
<input class="btn btn-default" style="padding: 14px;background: rgba(0, 0, 0, 0.2)" type="submit" value="Edit" ng-click="editProduct(product)" />
</td>
<td>{{product.Price}}</td>
<td>{{product.ProductName}}</td>
</tr>
</tbody>
</table>
</div>
</article>
</div>
</div>
<div ng-controller="Pagination">
<pagination total-items="totalItems" ng-change="pageChanged()" previous-text="Before" next-text="Next" first-text="First"
last-text="Last" ng-model="currentPage" items-per-page="itemPerPage" max-size="maxSize" class="pagination-sm" boundary-links="true"></pagination>
</div>
From the controller names, I bet the Pagination and ProductController controllers are both instantiated more or less at the same time, BEFORE invoking the .setproductTotalCount() method. If that is the case, then because you are treating the productTotalCount variable as a primitive type (instead of an object) after setting it, the changes do not get reflected between the controllers.
Try the following:
// Change the service to:
App.service("ProductService", function () {
var productTotalCount = {};
return {
getproductTotalCount: function () {
return productTotalCount;
},
setproductTotalCount: function (value) {
productTotalCount.value = value;
}
}
});
// In Pagination controller:
App.controller('Pagination', function ($scope, ProductService) {
debugger;
$scope.totalItems = ProductService.getproductTotalCount(); // this will still be an empty object initially, but when the value is updated in the service, the $scope.totalItems will also be updated
$scope.currentPage = 1;
$scope.itemPerPage = 8;
// this should confirm that changes are being propagated.
$scope.$watch('totalItems', function(newVal) {
console.log('totalItems updated. New Value:', newVal);
});
// NOTE: Keep in mind that the real productTotalCount will be stored as $scope.totalItems.value;
});
---- EDIT ----
Per your comment below, it proves that the solution above DOES work. To prove it, change:
$scope.$watch('totalItems', function(newVal) {
console.log('totalItems updated. New Value:', newVal);
});
to
$scope.$watch('totalItems', function(newVal) {
console.log('totalItems updated. New Value:', newVal);
console.log($scope.totalItems);
});
At that point, you should see that $scope.totalItems has been updated to:
{ value: 19 };
The issue may be how you're declaring your variable in your service. Because it's a local variable in the function rather than returned object, I believe it will be creating a new variable for each time you inject the service as a dependency. Try making the variable a member of the returned object. E.g.
App.service("ProductService", function () {
return {
productTotalCount: 0,
getproductTotalCount: function () {
return this.productTotalCount;
},
setproductTotalCount: function (value) {
this.productTotalCount = value;
}
}
});

Change the url without reloading the controller in Angularjs

Initially, I am showing all the users name in the table. Once the user selected any one of the name, I call the LoadData method with the selected user as the parameter. I am changing the url #/Trades/User/User1 and append the details under the User1. My requirement is 1) In the LoadData method, I want to change the url to as #/Trades/User/User1 or #/Trades/User/User2 based on the selection 2) It should update the data and reflect in view, but it should not reload the controller.
HTML
<div ng-app="myApp" ng-controller="myCtrl">
<table>
<tr ng-repeat-start="val in data.Titles" class="h" ng-click="LoadData(val.title)">
<td colspan="2" ng-hide="val.title == undefined">{{val.title}}</td>
</tr>
<tr ng-repeat="con in val.details">
<td>{{con.portfolio}}</td>
<td>{{con.status}}</td>
</tr>
<tr ng-repeat-end>
<td colspan="2">Load More</td>
</tr>
</table>
</div>
Code
angular.module("myApp", [])
.controller("myCtrl", ["$scope", function ($scope) {
$scope.data = {"Titles":[{title:"User 1",
details: [{portfolio: "Microsoft", status:"Active"},{portfolio:"IBM", status:"Inactive"}]
}, {title:"User 2",
details: [{portfolio: "Yahoo", status:"Inactive"},{portfolio: "Google", status:"Active"}]
}]};
$scope.LoadData = function(id) {
Change the url as #/Trades/Author/User1
Load the details of the User1
};
});
You could try this. I am using this currently in my project for something very similar. http://joelsaupe.com/programming/angularjs-change-path-without-reloading/
app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])

filter an item within ng-repeat angularjs

I want to create a filter to retrieve only the last digits from a URL, for example team._links.team.href gives back a URL such as http://api.football-data.org/alpha/teams/61 but I want only want it to give back 61. How could I do this?
HTML:
<tr ng-repeat="team in teamsList">
<td>{{$index + 1}}</td>
<td><a href="#/teams/{{team._links.team.href }}">
{{team.teamName}}
</a></td>
<td>{{team.playedGames}}</td>
<td>{{team.points}}</td>
<td>{{team.goals}}</td>
<td>{{team.goalsAgainst}}</td>
<td>{{team.goalDifference}}</td>
</tr>
CONTROLLER.js
angular.module('PremierLeagueApp.controllers', []).
controller('teamsController', function($scope, footballdataAPIservice) {
$scope.teamsList = [];
footballdataAPIservice.getTeams().success(function (response) {
//Dig into the response to get the relevant data
$scope.teamsList = response.standing;
});
});
You can add a simple function on the controller like this:
$scope.teamID = function(url) {
return url.substr(url.lastIndexOf('/') + 1);
};
and then use it in your repeat:
<a href="#/teams/{{teamID(team._links.team.href)}}">
Here's a fiddle.
One way to go is to define your own filter for this. For example like this:
.filter('idOnly', function() {
return function(input) {
return input.substring(input.lastIndexOf('/')+1)
};
})
which then can be used like this:
<td>
<a href="#/teams/{{team._links.team.href | idOnly }}">
{{team.teamName}}
</a>
</td>
A full code snippet is added below.
angular.module('PremierLeagueApp', [])
.filter('idOnly', function() {
return function(input) {
return input.substring(input.lastIndexOf('/')+1)
};
})
.controller('teamsController', function($scope) {
$scope.teamsList = [ { teamName : 'team1',
playedGames : 1,
points: 0,
goals: 1,
goalsAgainst: 4,
goalsDifference: 3,
_links : {
team : { href : "http://api.football-data.org/alpha/teams/61"}
}
},
{ teamName : 'team2',
playedGames : 1,
points: 3,
goals: 4,
goalsAgainst: 1,
goalsDifference: 3,
_links : {
team : {href : "http://api.football-data.org/alpha/teams/62"}
}
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="PremierLeagueApp">
<div ng-controller="teamsController">{{aap}}
<table>
<tr ng-repeat="team in teamsList">
<td>{{$index + 1}}</td>
<td><a href="#/teams/{{team._links.team.href | idOnly }}">
{{team.teamName}}
</a></td>
<td>{{team.playedGames}}</td>
<td>{{team.points}}</td>
<td>{{team.goals}}</td>
<td>{{team.goalsAgainst}}</td>
<td>{{team.goalDifference}}</td>
</tr>
</table>
</div>
</body>

Resources