AngularJS: Controller variable not updating despite being bound to service variable - javascript

Problem: A controller variable vm.boatList (intended to be bound to a service variable, SearchService.boatList) is not updating with the service variable.
Code Snippets: Complete code at the bottom.
Controller
function SearchController($stateParams, SearchService) {
var vm = this;
vm.boatList = SearchService.boatList;
Service
function SearchService(BoatsDataService) {
var service = {
boatList: null,
getBoatList: getBoatList,
params: {}
};
return service;
Additional Data:
The getBoatList function called from the controller works - it logs data from the SearchService to the console. When I log vm.boatList immediately afterward, it's null.
Also, <div ng-repeat="boat in SearchController.boatList">...</div> only works when I assign directly to vm.boatList.
Console Output:
Array[3]
null
Complete Controller:
(function () {
'use strict';
angular.
module('trips').
controller('SearchController', SearchController);
SearchController.$inject = ['$stateParams', 'SearchService'];
function SearchController($stateParams, SearchService) {
var vm = this;
vm.boatList = SearchService.boatList;
vm.initialParams = getParams();
activate();
////////////////////
function activate() {
setInitialParams();
SearchService.getBoatList()
.then(function (data) {
SearchService.boatList = data;
console.log(SearchService.boatList); // Logs data.
console.log(vm.boatList); // Logs null.
})
.catch(function (error) {
console.log(error);
});
}
function getParams() {
var params = {
location: $stateParams.location,
passengers: $stateParams.passengers,
maxPrice: $stateParams.maxPrice,
minPrice: $stateParams.minPrice,
embark: $stateParams.embark,
disembark: $stateParams.disembark,
category: $stateParams.category
};
return params;
}
function setInitialParams() {
SearchService.params = getParams();
}
}
})();
Complete Service:
(function () {
'use strict';
angular.
module('trips').
factory('SearchService', SearchService);
SearchService.$inject = ['BoatsDataService'];
function SearchService(BoatsDataService) {
var service = {
boatList: null,
getBoatList: getBoatList,
params: {}
};
//$scope.$watch('service.params', function () {
// getBoatList().then(function (data {
// service.boatList = data;
// })
// }, true
//);
return service;
//////////////////////
function getBoatList() {
return BoatsDataService.BoatList
.query(service.params)
.$promise
}
}
})();
Recap: I don't know how to two-way bind, I guess.

When you're trying to bind a variable that's directly on the service, it will fail (see call by sharing). However, if you wrap it with an object, it will work like you expect, see this Fiddle.
So in your case, if you bind an object inside the service to your controller, it should be fine -
Service:
var service = {
boatListObj : {
boatList: null
},
getBoatList: getBoatList,
params: {}
};
Controller:
vm.boatListObj = SearchService.boatListObj;
Promise response:
.then(function (data) {
SearchService.boatListObj.boatList = data;
console.log(SearchService.boatListObj.boatList); // Logs data.
console.log(vm.boatListObj.boatList); // Should log fine now
})
The reason why <div ng-repeat="boat in SearchController.boatList">...</div> is not working is because SearchController.boatList is not on the $scope. It either must be on the $scope, or addressed via ControllerAs syntax when it's placed on the controller.

Related

resolving business treatment in service with ngResource

I have an AngularJs application working with components and several modules. I created a plunker example to present my problem here.
I have my NavbarComponent where I declared my Controller where I inject my service called NavbarService.
In the NavbarService, I inject a factory resource to make my Rest call, once this call is made I'm trying to made some treatment on the response before returning it back to the controller, in this example I just apply a simple filter on it, but it doesn't work. If I omit my treatment and return only the categories, the code works and you can visualize a list of two.
I can make my treatment in the controller but this is a bad practice 'cause I believe it should be done in the Service, secondly since it's an asynchronous response I must do something like this to make it work, which is really really ugly:
navbarService.getCategories().$promise.then(function (response) {
console.log("controller", response[0].category);
vm.categories = categoryFilter(response[0].category);
}, function (error) {
console.log("an error occured");
});
Can someone please guide me through this, I'm out of solutions. Thank you
Another simple way is to pass a callback function to service from you component like this
'use strict';
angular.module('navbar').component('appNavbar', {
templateUrl: "navbar.template.html",
controller: [ 'navbarService', function appNavbarController(navbarService) {
var vm = this;
navbarService.getCategories(function(data){
// this will be called when service will get the response and filter function has filtered the response
vm.categories = data;
});
}]
});
Now service should be like this
'use strict';
angular.module('navbar').service('navbarService', ['categoryResourceService', 'categoryFilter', function(categoryResourceService, categoryFilter) {
var vm = this;
vm.getCategories = function(callback) {
categoryResourceService.query(function(response) {
console.log("[NavbarService] response:", response);
callback(categoryFilter(response));
}, function(error) {
console.log("[NavbarService] error:", error);
});
//return vm.categories;
}
}]);
Filter will be like this
'use strict';
angular.module('navbar').filter('category', function() {
return function(categories) {
var categoryIds = ['World'];
var result = [];
angular.forEach(categoryIds, function (categoryId) {
angular.forEach(categories, function (category) {
if (category.name == categoryId) {
console.log("Match");
result.push(category);
}
});
});
return result;
};
});
Your filter should be like this and it should be called in transformResponse in $resource query instead of service, i hope this will help you
'use strict';
angular.module('navbar').filter('category', function() {
return function(categories) {
var categoryIds = ['World'];
var result = [];
angular.forEach(categoryIds, function (categoryId) {
angular.forEach(categories, function (category) {
if (category.name == categoryId) {
console.log("Match");
result.push(category);
}
});
});
return result;
};
});
Your categoryResource.service should be like this
angular.module('shared').factory('categoryResourceService',
['$resource','categoryFilter', function($resource, categoryFilter) {
var provider = "categories.json";
var params = {
id: '#id'
};
return $resource(provider, params, {
query: {
isArray: true,
method: 'GET',
params: {},
transformResponse: function(categories) {
var results = categoryFilter(angular.fromJson(categories));
console.log("[categoryResourceService] filtered response:", results);
return results;
}
}
});
}]);
navbar.service should be like this simply
'use strict';
angular.module('navbar')
.service('navbarService', [ 'categoryResourceService', function (categoryResourceService) {
var vm = this;
vm.getCategories = function(){
vm.categories = categoryResourceService.query(function(response){
console.log("[NavbarService] response:", response);
}, function(error){
console.log("[NavbarService] error:", error);
});
return vm.categories;
}
}]);
And components like this
'use strict';
angular.module('navbar').component('appNavbar', {
templateUrl: "navbar.template.html",
controller: [ 'navbarService', function appNavbarController(navbarService) {
var vm = this;
vm.categories = navbarService.getCategories();
}]
});

angular get and set atributes via service

i can't find a solution to this, basicly everytime i do a login, i want to store the user that i get from the node end point in the service, after that in my main Controller i should get the name of the user, but that never happen, dunno why
here is the code:
app.controller('MainCtrl', function ($scope, $state,$location,$http,user) {
$scope.user = {
nome: user.getProperty()
};
$scope.showRegister = function () {
$state.go('register');
}
$scope.showLogin = function () {
$state.go('login');
}
});
app.controller('loginController', function ($scope, $http, $state,user) {
$scope.login = function () {
var data = {};
data.password = $scope.loja.password;
data.email = $scope.loja.email;
$http.post('http://localhost:8080/login/',data)
.success(function (data) {
console.log(data);
user.setProperty(data.nome);
$state.go('home');
})
.error(function (statusText) {
console.log("failed");
});
}
});
user service
app.service('user', function () {
var property = {};
return {
getProperty: function () {
return property.nome;
},
setProperty: function (value) {
property.nome = value;
}
};
});
You could just watch your service for changes by adding this code to your MainCtrl:
$scope.$watch(function () { return user.getProperty();}, updateProp, true);
function updateProp(newValue, oldValue) {
$scope.user = {
nome: newValue
};
}
updateProp gets executed everytime the value of user.getProperty() changes.
Your main issue is with your MainCtrl . In the initial execution of MainCtrl there is no value set into your service so its get blank. MainCtrl executes before setting the value in the service.
$scope.user = {
nome: user.getProperty()
};
this code should be executed after setting the value in the service but it executes in the initialization of controller.
You can get the reference from the fiddle below.
http://jsfiddle.net/ADukg/9799/

How can I pass variable from one function to another in angularJS?

$scope.openModal = function (page, size) {
console.log(page); // this is working
$uibModal.open({
animation: true,
templateUrl: 'app/pages/servers/newRole.html',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
};
$scope.hello = function() {
console.log(page); // need value of page from above function
var btn = document.createElement("BUTTON");
var t = document.createTextNode("ABC");
btn.classList.add("btn-primary", 'btn-xs', 'btn');
btn.appendChild(t);
document.getElementById('Id1').appendChild(btn);
}
I have tried using a global variable and assigning the page to this but it is saying undefined.
I can access this variable by calling hello(page) inside openModal, but that won't work as it will call hello() when it's not needed.
I have two buttons and calling openModal function on click of 'btn1', and passing page parameter on this button and then there is another button inside that modal 'btn2', calling hello() on click of btn2.
Tl;Dr : use your scope, or use a service.
If those methods are in the same controller:
You should just be able to pass it through a variable:
Initialize it : $scope.data = {};
Push some data in it: $scope.data = anything; or $scope.data.field = anything.
Use it further in your function : $scope.data ... \\ Do anything
If they're not:
You can use an AngularJS service. When you finished your data treatment, you can save it into your service, then getting it back later. View it as a getter/setter for any data you would like. Example:
A dummy AngularJS service:
var service = angular.module('yourService', [])
.factory('$yourService', function () {
var yourdata = {};
return {
setData: function(data) {
yourdata = data;
},
getData: function() {
return data;
}
};
});
return service;
Then, be sure that you inject it into your controller. Into your first function, you can call your service as:
$yourService.setData(anyData);
And get back the data in your second function: $yourService.getData();
$scope.openModal = function (page, size) {
console.log(page); // this is working
$uibModal.open({
animation: true,
templateUrl: 'app/pages/servers/newRole.html',
controller: ModalInstanceCtrl, //resolved items can be use in this controller
size: size,
resolve: {
items: function () {
return page; // this will return value
}
}
});
};
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, items) {
$scope.hello = function() {
console.log(items); // Here you can get value
}
});

Array data from ui-router state resolve not accessible from within controller after injection

This seems so simple yet I have been banging my head against it for hours now...
I have an angular 1.2.29 module that uses ui-router to resolve a data dependency from a service and inject it into a named nested view controller. The service returns my data object exactly as desired and I can inject that returned data into my view controller and log it again with no issue.
What I am baffled by is that if I inspect the object in the console, I can see the array I am trying to access on one of the nested properties of the object I injected from the service (this is from within the context of the controller I am trying to use it in).
However, if I try and pass that array of data into another method in the same controller (in this case a d3 method to draw a graph) - I get nothing but a empty string where the array is.
I can access every other property on the object I passed into the method just fine, and so long as I don't try and access the array directly it is 'visible' to me in my logging, but when I try and pass the array directly in I get an empty string.
I have tried everything I can from making a deep copy or pushing the array into a new object and accessing it that way, trying to cast that property as an array (again into a new object), and a whole host of other random hacks I have run across all to no avail.
I feel like I am missing something incredibly trivial here...
These three logs are called on the same object from within the same method one after the other and you can see the issue.
I am trying to pass the data.datasources.tabledata.datum array to another method that expects an array, but all I can get is the empty string.
Note: 'data' is the object returned from the service resolve at the state transition and injected into the view controller.
> console.log(data);
// Expected object.
> Object {datasources: Object}
datasources: Object
tabledata: Object
datum: Array[9383]
source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv"
__proto__: Object
__proto__: Object
__proto__: Object
> console.log(data.datasources);
// Expected object.
> Object {tabledata: Object}
tabledata: Object
datum: Array[9383]
source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv"
__proto__: Object
__proto__: Object
> console.log(data.datasources.tabledata);
// String instead of array... WTH???
> Object {source: "./data/BSGAM_Heads_Wells_Drains_Zones_Master.csv", datum: ""}
> console.log(data.datasources.tabledata.datum);
// Returns NOTHING - not even undefined... just an empty log line #_#
>
UPDATE: Here is the service loading the data (it is my own)
(function() {
'use strict';
angular
.module('mcsdss.providers')
.factory('FormulationRetrieval', FormulationRetrieval);
FormulationRetrieval.$inject = ['$http'];
function FormulationRetrieval($http) {
FormulationRetrieval.getFormulation = function (target) {
var promise = $http
.get(target)
.then(function (response) {
return FormulationRetrieval.configureFormulation(response.data);
});
return promise;
};
FormulationRetrieval.configureFormulation = function (f) {
FormulationRetrieval.formulationContainer = f;
FormulationRetrieval.loadFormulationSourceData(FormulationRetrieval.formulationContainer);
FormulationRetrieval.loadFormulationGisData(FormulationRetrieval.formulationContainer);
return FormulationRetrieval.formulationContainer;
};
FormulationRetrieval.loadFormulationSourceData = function (fc) {
function parseFormulationDatasource(fd, destination) {
Papa.parse(fd, {
complete: function(results) {
destination.datum = results.data;
}
});
}
function loadData(target) {
var promise = $http
.get(target.source)
.then(function (response) {
parseFormulationDatasource(response.data, target);
});
return promise;
}
var datasources = [fc.datagridConfig.datasources.tabledata, fc.graphConfig.datasources.graphContextData];
angular.forEach(datasources, loadData);
};
FormulationRetrieval.loadFormulationGisData = function (fc) {
function loadGeodata(target) {
angular.forEach(target, function(value, key) {
var promise = $http
.get(value.source)
.then(function (response) {
value.datum = response.data;
});
return promise;
});
}
var datasources = [fc.mapConfig.datasources.geojson];
angular.forEach(datasources, loadGeodata);
};
FormulationRetrieval.getAnalysisConfig = function (fc) {
var analysisConfig = fc.analysisConfig;
return analysisConfig;
};
FormulationRetrieval.getMaufConfig = function (fc) {
var maufConfig = fc.maufConfig;
return maufConfig;
};
FormulationRetrieval.getGraphConfig = function (fc) {
var graphConfig = fc.graphConfig;
return graphConfig;
};
FormulationRetrieval.getTableConfig = function (fc) {
var tableConfig = fc.datagridConfig;
return tableConfig;
};
FormulationRetrieval.getMapConfig = function (fc) {
var mapConfig = fc.mapConfig;
return mapConfig;
};
return FormulationRetrieval;
}
})();
Here is the ui-router resolve on the state:
(function() {
'use strict';
angular
.module('analyze')
.config(DashboardRoutes);
DashboardRoutes.$inject = ['$stateProvider'];
function DashboardRoutes($stateProvider) {
// Define states.
var analyze_state = {
abstract: true,
url: '/analyze',
templateUrl: 'modules/analyze/views/analyze.client.view.html',
controller: 'AnalyzeViewController',
controllerAs: 'analyze',
data: {
title: 'Analyze'
},
resolve: {
analysisData: function(FormulationRetrieval) {
return FormulationRetrieval.getFormulation('./data/formulations/bs.formulation.json');
},
analysisConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getAnalysisConfig(analysisData);
},
maufConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getMaufConfig(analysisData);
},
tableConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getTableConfig(analysisData);
},
graphConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getGraphConfig(analysisData);
},
mapConfig: function(FormulationRetrieval, analysisData) {
return FormulationRetrieval.getMapConfig(analysisData);
}
}
};
var analyze_layout_state = {
abstract: false,
url: '',
views: {
'graph': {
templateUrl: 'modules/analyze/views/analyze.graph.client.view.html',
controller: 'GraphViewController'
},
'map': {
templateUrl: 'modules/analyze/views/analyze.map.client.view.html',
controller: 'MapViewController'
},
'filters': {
templateUrl: 'modules/analyze/views/analyze.filters.client.view.html',
controller: 'FiltersViewController',
controllerAs: 'filters'
},
'datatable': {
templateUrl: 'modules/analyze/views/analyze.datatable.client.view.html',
controller: 'DatatableViewController',
controllerAs: 'datatable'
}
}
};
// Populate provider.
$stateProvider
.state('analyze', analyze_state)
.state('analyze.layout', analyze_layout_state);
}
})();
And here is the controller receiving the injected data:
(function() {
'use strict';
angular
.module('analyze')
.controller('AnalyzeViewController', AnalyzeViewController);
AnalyzeViewController.$inject = ['$rootScope', '$scope', '$state', '$location', 'Authentication', 'httpq', 'analysisData', 'maufConfig', 'tableConfig', 'graphConfig', 'mapConfig'];
function AnalyzeViewController($rootScope, $scope, $state, $location, Authentication, $httpq, analysisData, analysisConfig, maufConfig, tableConfig, graphConfig, mapConfig) {
// This provides Authentication context.
$scope.authentication = Authentication;
$scope.currentRoute = 'Analyze';
// console.log($scope.currentRoute);
// ALL OF THESE ARE INJECTED AND APPEAR TO BE CORRECT IN CONSOLE.
// console.log(analysisData);
// console.log(analysisConfig);
// console.log(maufConfig);
// console.log(tableConfig);
// console.log(graphConfig);
// console.log(mapConfig);
// NOTE: At one point I was loading the data through promises inside the
//controller, but moved it into the state resolve for better SOC.
//Strangely the $broadcast of the exact same value done here in the finally()
//block of the $httpq method works - using the new injected data object!
//And yet, the same $broadcast on $stateChangeSuccess (which DOES send the correct
//data into the listening subscribers if I only send the entire object) sends only
//empty string if I specify the array.
// Manual data loading.
$scope.sourceFile_A = './data/BSGAM_Heads_Wells_Drains_Zones_Master.csv';
$httpq.get($scope.sourceFile_A)
.then(function(data) {
// ...removed because not used.
})
.catch(function(data, status) {
console.error('Load error', response.status, response.data);
})
.finally(function() {
// Works here using the injected resolved promise, does not work in stateChangeSuccess... WTH??
$scope.$broadcast('analysisDataLoaded', analysisData.datagridConfig.datasources.tabledata.datum);
});
$scope.$on('$stateChangeSuccess', function() {
// EXACT SAME BROADCAST AS ABOVE FAILS HERE - EMPTY STRING.
// $scope.$broadcast('analysisDataLoaded', analysisData.datagridConfig.datasources.tabledata.datum);
});
// extra code removed...
})();
Lastly the Nested View Controller (one of 4) that I am trying to get to use the data via the $broadcast from the abstract parent. I can also access all the injected objects here directly without the broadcast but I am trying to decouple as much as possible as several views need to update in sync from the broadcast.
(function() {
'use strict';
angular
.module('analyze')
.controller('DatatableViewController', DatatableViewController);
DatatableViewController.$inject = ['$scope', 'Authentication', '$filter', 'ngTableParams', 'AnalysisDataFactory', 'tableConfig'];
function DatatableViewController($scope, Authentication, $filter, ngTableParams, AnalysisDataFactory, tableConfig) {
// This provides Authentication context.
$scope.authentication = Authentication;
// Expose public API.
$scope.headerFilter = headerFilter;
$scope.datasetOrder = datasetOrder;
$scope.rowClicked = rowClicked;
$scope.decorateSiblings = decorateSiblings;
$scope.clearSiblings = clearSiblings;
$scope.updateView = updateView;
// Private members.
$scope.headerdata = [];
$scope.tabledata = [];
$scope.suf01 = 0;
$scope.suf02 = 0;
$scope.suf03 = 0;
$scope.muf = 0;
$scope.$on('analysisDataLoaded', function(event, args) {
console.log('analysisDataLoaded...', event, args);
$scope.updateView(args);
// THIS IS WHAT I WANT TO DO INSTEAD:
// $scope.updateView(tableConfig); // Not working yet.
});
function headerFilter(target) {
return target.visible;
}
function datasetOrder(key) {
angular.forEach($scope.headers, function(target) {
// console.log('key='+key);
if (target.data == key) {
if (target.visible) {
return target.order;
}
}
});
return -1;
}
function rowClicked(target) {
for (var key in target) {
if (target.hasOwnProperty(key)) {
console.log(key + ' -> ' + target[key]);
}
}
}
function decorateSiblings(target) {
// console.log('data row touched, sending emission.');
$scope.$emit('currentDatatableTarget', target);
}
function clearSiblings(target) {
// console.log('datarow cleared, sending all clear.');
$scope.$emit('clearDatatableTarget', target);
}
function updateView(data) {
// ngTable
$scope.dataTable = new ngTableParams({
page: 1,
count: 10
}, {
total: $scope.tabledata.length,
counts: [10, 25, 50, 100, 250],
defaultSort: 'asc',
getData: function($defer, params) {
$scope.data = params.sorting() ? $filter('orderBy')($scope.tabledata, params.orderBy()) : $scope.tabledata;
$scope.data = params.filter() ? $filter('filter')(data, params.filter()) : data;
$scope.data = data.slice((params.page() - 1) * params.count(), params.page() * params.count());
$defer.resolve($scope.data);
}
});
}
}
})();
Something simple in here - probably a JS gotcha that I am overlooking.... Thanks in advance for any input!!
Turns out it was from not properly deferring and resolving the promises before trying to bind to them. I still have not identified exactly where that timing is going wrong in my code, but I can now intermittently load the data as I intended and can see that it is clearly a function of async inconsistencies. The $broadcast event is working just fine (when the async data loads properly).

Jasmine test cases for inner function calls returning response

I am trying to write the jasmine test case for a function which in turn calls another function and returns a response. Seems like the function is called but i am not getting the value that is set inside the function.
Controller Function:
angular.module('serviceApp')
.controller('startWorkscopeCtrl', function ($rootScope, $log, $scope, CommentService) {
$scope.getTextById = function (id) {
CommentService.get({ id: id }, function (response) {
$scope.text = response;
$scope.textCount = $scope.text.length;
});
};
});
The service is referenced in another js file where it makes the rest service call to the backend service.
angular.module('restServiceModule', ['ngResource'])
.factory('CommentService', function($resource){
return $resource(
{
get: {
method: 'GET',
isArray: true,
url: window.urlPrefix + '/commentservice/:id',
params: {
id: '#id'
}
}
});
})
Test Case:
var mockTextComment;
var fakeText = ['testPass'];
beforeEach(function(){
mockTextComment = {
get: function(){
return fakeText;
}
};
});
it('should get comments by asset id', function () {
spyOn(mockTextComment, 'get');
inject(function ($controller) {
ctrl = $controller('startWorkscopeCtrl', {
$scope: scope,
CommentService: mockTextComment
});
scope.getTextById(40107);
expect(scope.textCount).toEqual(1);
});
});
With this, i am getting the below error:
**Expected undefined to equal 1.
Error: Expected undefined to equal 1.**
Not sure where i am doing wrong. Any pointers on this will be helpful.
It looks like CommentService.get does not return a value; it calls back a function upon completion. Therefore, a CommentService mock would look like this:
var mock = {
get : function(arg1, callback){
callback(fakeText);
}
}

Categories

Resources