Data Binding not happening between service and controller in angularjs - javascript

I am trying to use a service instead of a factory to perform two way data binding, I have seen may tutorials online on using a factory but I prefer using a service instead of a factory, so far I have come up with the following pattern to perform the binding but, on doing console.log() i found that the data from the service doesn't bind to the data on the controller.
controller: ['$scope','LeadsService','ServiceVehicleModels', function($scope, LeadsService, ServiceVehicleModels) {
$scope.colorList = ServiceVehicleModels.colors;
$scope.yearsList = ServiceVehicleModels.years;
$scope.$watch(function() { return ServiceVehicleModels.colors }, function(data) {
return $scope.colorList = data.colors;
},true);
};
Here is my service code
exports.service = function() {
this.colors = [];
this.years = [];
this.trims = [];
var scope = this;
this.setColors = function(colorsArr) {
scope.colors = colorsArr;
};
this.setYears = function(yearsArr) {
scope.years = yearsArr;
};
};
What changes do i need to make to make the data binding work?

You have issue in watcher. This should solve bindings:
$scope.$watch(function() { return ServiceVehicleModels.colors }, function(colors) {
return $scope.colorList = colors;
},true);

Related

angularjs 1.6 two way binding of model doesn't update when changed inside a non angular event

angularjs code
var app = angular.module("testApp", []);
app.controller('captureCtrl', function () {
var vm = this;
vm.obj = {
show: false
};
addressControls.control.listen('populate', function (address, variations) {
vm.line1 = address.Line1;
vm.line2 = address.Line2;
vm.city = address.City;
vm.postcode = address.PostalCode;
vm.obj.show = true;
});
vm.test = function () {
vm.obj.show = true;
}
});
vm.obj.show value in the view doesn't get updated when changed inside the 'populate' event, but it does get updated inside vm.test function. How can I get this to work and why won't binding get updated in the view? I'm using angularjs 1.6.
Try $scope.apply().
$scope.$apply(function () {
//Your Code here
});
EDIT 1: I'm sorry--I completely overlooked something. You're using "this" instead of "$scope".
EDIT 2: Since it's a non-angular function, you definitely need a $scope.$apply() in there.
Try doing this instead:
var app = angular.module("testApp", []);
app.controller('captureCtrl', function ($scope) {
$scope.obj = {
show: false
};
addressControls.control.listen('populate', function (address, variations) {
$scope.line1 = address.Line1;
$scope.line2 = address.Line2;
$scope.city = address.City;
$scope.postcode = address.PostalCode;
$scope.obj.show = true;
$scope.$apply();
});
$scope.test = function () {
$scope.obj.show = true;
};
});
In general, use "$scope" if you want to expose properties for your DOM to work with.

Is It possible to inject a scope variable from a controller into a service function Angular?

angular.module('store_locator')
.constant("baseURL","http://locator.sas.dev.atcsp.co.za/api/")
.service('stationsService', ['$http', 'baseURL', function($http,baseURL) {
this.getStations = function(){
return $http.get(baseURL+"station.json");
};
this.getStation = function (index) {
return $http.get(baseURL+"station/"+index+".json");
};
}]);
I have here a service with 2 functions, I need to amend the getStations function in some way to allow the usage of the below:
$scope.filterList = [];
$scope.change = function(subproduct, active){
if (active){
$scope.filterList.push(subproduct.subproduct_id);
$scope.filterPush = "?subproducts[]="+$scope.filterList.join("&subproducts[]=");
}
else{
$scope.filterList.splice($scope.filterList.indexOf(subproduct), 1);
$scope.filterPush = "?subproducts[]="+$scope.filterList.join("&subproducts[]=");
}
};
I need to append the $scope.filterList here:
return $http.get(baseURL+"station.json"+$scope.filterList);
The reason for this is that i have to do a new $http call on submit of a search button. I hope I'm descriptive enough.
No. You cannot inject $scope in your service. Expose the desired method in your service and pass the required data (which is in your scope) as an argument. Call the service method from your change method in the controller.
Service
this.getStations = function(filterList){
return $http.get(baseURL+"station.json"+filterList);
};
Controller
$scope.change = function(subproduct, active){
if (active){
$scope.filterList.push(subproduct.subproduct_id);
$scope.filterPush = "?subproducts[]="+$scope.filterList.join("&subproducts[]=");
}
else{
$scope.filterList.splice($scope.filterList.indexOf(subproduct), 1);
$scope.filterPush = "?subproducts[]="+$scope.filterList.join("&subproducts[]=");
}
stationsService.getStations($scope.filterList);
}

Factory Array can be pushed but not replaced

I have this factory
DatosFactory.js
(function() {
'use strict';
angular.module('InmoManager')
.factory('DatosFactory', function($http, $location) {
var datos = {
propiedadesFiltradas: []
}
...
datos.getPropiedadesFiltradas = function(){
return datos.propiedadesFiltradas;
}
datos.setPropiedadesFiltradas = function(data){
datos.propiedadesFiltradas.length = 0;
datos.propiedadesFiltradas.push(data);
}
return datos;
})
})();
This controller
SidebarController.js
(function() {
'use strict';
angular.module('InmoManager')
.controller('SidebarController', function($http, $scope, DatosFactory) {
var sidebarCtrl = this;
sidebarCtrl.toggleSidebar = function(){
$('#wrapper').toggleClass('toggled');
}
sidebarCtrl.propiedades = DatosFactory.getPropiedadesFiltradas();
});
})();
Making this work
pageSidebar.html
<li class="item" ng-repeat="propiedad in sidebarCtrl.propiedades[0] | orderBy:'titulo'">
This works great!, but, i want to change this:
ng-repeat="propiedad in sidebarCtrl.propiedades[0]"
to
ng-repeat="propiedad in sidebarCtrl.propiedades"
and this
datos.setPropiedadesFiltradas = function(data){
datos.propiedadesFiltradas.length = 0;
datos.propiedadesFiltradas.push(data);
}
to
datos.setPropiedadesFiltradas = function(data){
datos.propiedadesFiltradas = data;
}
But when i make this, the variable sidebarCtrl.propiedades get's undefined (doesn't update when i call datos.setPropiedadesFiltradas()
When you reassign datos.propiedadesFiltradas = data; you break the object reference, and as the result datos.propiedadesFiltradas is no longer points to the object Angular set up bindings to.
On the other hand, when you push object reference stays untouched, and Angular's changes tracking engine can detect changes and rerender view.
One more thing. I would recommend you to use ngClass directive instead of $('#wrapper').toggleClass('toggled');:
sidebarCtrl.toggleSidebar = function() {
sidebarCtrl.toggle = !sidebarCtrl.toggle;
}
and in HTML
<div id="wrapper" ng-class={toggled: toggle}>...</div>
Because of dfsq answer i assume that all i wanted it's not possible, but i found this "partial" solution
With this in the factory:
datos.setPropiedadesFiltradas = function(data){
datos.propiedadesFiltradas.length = 0;
datos.propiedadesFiltradas.push.apply(datos.propiedadesFiltradas,data);
}
i could change this:
ng-repeat="propiedad in sidebarCtrl.propiedades[0]"
to
ng-repeat="propiedad in sidebarCtrl.propiedades"

Changing controller $scope vars dynamically

So. I have simple controller and service:
angular
.module('field', [])
.controller('FieldController', function(FieldService) {
var vm = this;
vm.name = FieldService.getName();
})
.service('FieldService', function() {
var name = 'John'
this.getName = function() {
return name;
};
this.setName = function(newName) {
name = newName;
};
})
;
Then i have some $interval in anotherService, that getting data every 1 second and calling FieldService.setName:
.service('UpdateService', function($http, FieldService) {
$interval(function() {
$http.get('/somUrl')
.then(function(response) {
FieldService.setName(response.name);
});
});
})
But it won't change my HTML.
If i switch from primitive to object in returning value getName, then it's working.
Is there another approach? I personally think, that this structure i created is bad, but can't understand how it should be done.
JavaScript is always pass-by-value, but when your variable is an object, the 'value' is actually a reference to the object. So in your case, you are getting a reference to the object, not the value. So when the object changes, that change isn't propagated like a primitive would be.
Your code seems a bit incorrect, too. You are setting the value of response.name to FieldService.setName, which is actually a function.
If you want to use the getter/setter approach you have listed, then you could use events to let the controller know that name has changed.
.service('UpdateService', function($http, FieldService, $rootScope) {
$interval(function() {
$http.get('/somUrl')
.then(function(response) {
FieldService.setName(response.name);
$rootScope.$broadcast('nameChanged', {
name : response.name
});
});
});
})
.controller('FieldController', function(FieldService, $scope) {
var vm = this;
vm.name = FieldService.getName();
$scope.$on('nameChanged', function (evt, params) {
vm.name = params.name;
});
})
Another way to accomplish this is to use a $scope.$watch on the service variable in the controller:
.controller('FieldController', function($scope, FieldService) {
$scope.name = FieldService.getName();
$scope.$watch(function () {
return FieldService.getName();
}, function (newVal, oldVal) {
if (newVal !== oldVal) {
$scope.name = newVal;
}
});
})
I would move my $interval function inside a controller and then just update a $scope attribute every second. Then Angular will take care of the rendering.. Or you must also use an $interval function in your controller which gets the service content (ie FieldService.getName) every second.
I would use it this way:
angular
.module('field', [])
.controller('FieldController', function($scope, FieldService) {
$scope.name = function(){
FieldService.getName();
};
})
.service('FieldService', function() {
var name = 'John'
this.getName = function() {
return name;
};
this.setName = function(newName) {
name = newName;
};
});
Use name() in your html to see the update value.And your other service:
.service('UpdateService', function($http, FieldService) {
$interval(function() {
$http.get('/somUrl')
.then(function(response) {
FieldService.setName(response.name);
});
}, 1000);
})
There are numerous ways in which you can achieve this. No way is the best way. Depends on person to person.
Hope this helps.
There are several ways to solve that problem.
1) Move the $interval to controller.Then you will have a variable, which holds that data and you can bind it in view
2) You can use AngularJs Events.$rootScope will help you to send signal and catch it wherever you want.
If you want more info about this solutions, you can see it here:
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html

Updating angular.js service object without extend/copy possible?

I have 2 services and would like to update a variable in the 1st service from the 2nd service.
In a controller, I am setting a scope variable to the getter of the 1st service.
The problem is, the view attached to the controller doesn't update when the service variable changes UNLESS I use angular.extend/copy. It seems like I should just be able to set selectedBuilding below without having to use extend/copy. Am I doing something wrong, or is this how you have to do it?
controller
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
$scope.building = BuildingsService.getSelectedBuilding();
});
service 1
app.factory('BuildingsService', function() {
var buildingsList = [];
var selectedBuilding = {};
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
angular.extend(selectedBuilding, _.find(
buildingsList, {'building_id': buildingId})
);
};
var getSelectedBuilding = function() {
return selectedBuilding;
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
service 2
app.factory('AnotherService', function(BuildingsService) {
...
// something happens, gives me a building id
BuildingsService.setSelectedBuilding(building_id);
...
});
Thanks in advance!
When you execute this code:
$scope.building = BuildingsService.getSelectedBuilding();
$scope.building is copied a reference to the same object in memory as your service's selectedBuilding. When you assign another object to selectedBuilding, the $scope.building still references to the old object. That's why the view is not updated and you have to use angular.copy/extend.
You could try the following solution to avoid this problem if you need to assign new objects to your selectedBuilding:
app.factory('BuildingsService', function() {
var buildingsList = [];
var building = { //create another object to **hang** the reference
selectedBuilding : {}
}
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
//just assign a new object to building.selectedBuilding
};
var getSelectedBuilding = function() {
return building; //return the building instead of selectedBuilding
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
With this solution, you have to update your views to replace $scope.building bindings to $scope.building.selectedBuilding.
In my opinion, I will stick to angular.copy/extend to avoid this unnecessary complexity.
I dont believe you need an extend in your service. You should be able to watch the service directly and respond to the changes:
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
// first function is evaluated on every $digest cycle
$scope.$watch(function(scope){
return BuildingsService.getSelectedBuilding();
// second function is a callback that provides the changes
}, function(newVal, oldVal, scope) {
scope.building = newVal;
}
});
More on $watch: https://code.angularjs.org/1.2.16/docs/api/ng/type/$rootScope.Scope

Categories

Resources