$scope$watch change one var if another changed - angularjs

so i have to scope objects
$scope.hf_directory = data.features;
$scope.brylanty = data.features;
Actually they contain the same data, but i am trying to know how watch works so then i can use it in my project.
And i would like to watch hf_directory on which i use filter:search, so it would update scope.brylanty after typing something in input fiels ng-model="search"
i tried to do something like this but not making any changes
$scope.$watch('hf_directory', function (newValue, oldValue, $scope) {
if(newValue) {
$scope.brylanty = newValue;
}
});
Both scope objects are displayed by ng-repeat loop, on first i use filter:search on another nothing, i know i could just use another filter:search, but i want to learn how to use watch ;)
My objects are geojson data, they containt the same value, geojson look like below:
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "name": "SomeName"}, "geometry": { "type": "Point", "coordinates": [ 11.410585, 11.293361 ] } },
{ "type": "Feature", "properties": { "name": , "SecondName": { "type": "Point", "coordinates": [ 11.410585, 11.293361 ] } }, .......
]
}
After Your advices guys i tried this
$scope.search = {};
$scope.$watch('search', function (newVal) {
$scope.brylanty = newVal;
});
And this
$scope.$watch('search', function (newValue, oldValue, $scope) {
if (newValue) {
$scope.brylanty = newValue;
}
});
But without any good results, in both cases when i start typing something object brylanty is disappearing?

If you want to make changes upon typing something in the input ng-model="search" then ideally you should be watching the search variable. It should be
$scope.$watch('search', function (newValue, oldValue, $scope) {
if(newValue) {
$scope.brylanty = newValue;
}
});
This can also be written as (for 2 lines less)
$scope.$watch('search', function (newValue, oldValue, $scope) {
$scope.brylanty = (newValue) ? newValue : $scope.brylanty;
});
alternatively you could use ng-change in your input field and have
<input ng-model="search" ng-change="brylanty = (search) ? search : brylanty"/>
Watching hf_directory has no effect since typing in search doesn't affect it.
Edit: if you really want to watch hf_directory and it's an object/array then use
$scope.$watchCollection()
with the same arguments

UPDATE Added Demo
According to Docs
You can watch any variable by doing something like
$scope.$watch(function(){
return $scope.hf_directory; // or Directories.hf_directory if Directory is a factory/service
}, function(newVal, oldVal){
if(newVal != oldVal){
$scope.newestValue = newVal; // Do some cool UI
}
}, true); // true to check object equality (see below)
object equality is equivalent to angular.equal Source
I hope this helps.

Related

AngularJs: How to pass scope from within $http.get, into directive?

I'm having trouble trying to populate a vector map from data in my database, using angular & directives.
I have an angular dashboard webpage that needs to display an svg vector map of the United States populated with data from my database. I'm following this tutorial, and everything works fine, however the tutorial passes hard coded values to the map. I need to modify this to accept database values, & that's where I'm having trouble. How can I make a call to my database and pass those values back to map, using a directive?
I'm still new to angular & this is my first experience with directives, but it appears as if the $http.get call happens after the directive, so I'm returning my db data too late. Here's my code:
App.js
Here, I'm using both directives for this map functionality. This all works fine:
var app = angular.module('dashboardApp', ['ngRoute', 'angularjs-dropdown-multiselect']);
app.controller('dashboardController', DashboardController);
app.directive('svgMap',function ($compile) {
return {
restrict: 'A',
templateUrl: '/Content/Images/Blank_US_Map.svg',
link: function (scope, element, attrs) {
var regions = element[0].querySelectorAll('path');
angular.forEach(regions, function (path, key) {
var regionElement = angular.element(path);
regionElement.attr("region", "");
regionElement.attr("dummy-data", "dummyData");
$compile(regionElement)(scope);
})
}
}
});
app.directive('region', function ($compile) {
return {
restrict: 'A',
scope: {
dummyData: "="
},
link: function (scope, element, attrs) {
scope.elementId = element.attr("id");
scope.regionClick = function () {
alert(scope.dummyData[scope.elementId].value);
};
element.attr("ng-click", "regionClick()");
element.attr("ng-attr-fill", "{{dummyData[elementId].value | map_color}}");
element.removeAttr("region");
$compile(element)(scope);
}
}
});
DashboardController.js
This is where my problem is. Here, I'm returning data from my database via an $http.get. I currently have my $scope.createDummyData function outside of this http.get & it works fine. If I place that code inside my $http.get however, data doesn't populate. And that's my problem:
var DashboardController = function ($scope, $http) {
$http.get('/Account/GetDashboardDetails')
.success(function (result) {
//need to place my $scope.createDummyData inside here
})
.error(function (data) {
console.log(data);
});
var states = ["AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL",
"IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM",
"NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI", "VA",
"WA", "WV", "WI", "WY"];
$scope.createDummyData = function () {
var dataTemp = {};
angular.forEach(states, function (state, key) {
dataTemp[state] = { value: Math.random() }
});
$scope.dummyData = dataTemp;
};
$scope.createDummyData();
};
HTML
Finally, here's my html. I don't feel anything here pertains to my issue, but I included it anyway, just in case:
<div class="container">
<div svg-map class="col-md-8 col-sm-12 col-xs-12" style="height:350px;"></div>
<p>
<button ng-click="createDummyData()" class="btn btn-block btn-default">Create Dummy Data</button>
</p>
<div class="regionlist">
<div ng-repeat="(key,region) in dummyData">
<div>{{key}}</div>
<div>{{region.value | number}}</div>
</div>
</div>
</div>
How can I go about populating my map, via database data?
Thanks
The link function of the directive executes before the $http.get XHR receives a response. One approach is to use $watch to wait for the data:
app.directive('region', function ($compile) {
return {
restrict: 'A',
scope: {
dummyData: "="
},
link: function (scope, element, attrs) {
scope.elementId = element.attr("id");
scope.regionClick = function () {
alert(scope.dummyData[scope.elementId].value);
};
element.attr("ng-click", "regionClick()");
element.attr("ng-attr-fill", "{{dummyData[elementId].value | map_color}}");
element.removeAttr("region");
//Use $watch to wait for data
scope.$watch("::dummyData", function(value) {
if (value) {
$compile(element)(scope);
};
});
}
}
});
In the above example, the watch expression uses a one-time binding so that the $compile statement executes only once. If one wants the element to be re-compiled on every change of the data, it gets more complicated.
For more information on $watch, see AngularJS $rootscope.Scope API Reference - $watch
Not sure if this is what you're looking for exactly but might help.
I would create a service to grab the data (I'm assuming the HTTP to /Account/GetDashboardDetails returns a list of states) then call the service in your controller. Once the call is finished, you then call your create dummyDataFunction.
// Service
angular
.module('dashboardApp')
.service('$dataService', dataService);
dataService.$inject = ['$http']
function dataService() {
var service = {
getData : getData
}
return service;
//////////
function getData() {
return $http.get('/Account/GetDashboardDetails')
.then(function (result) {
return result;
},function(error) {
return error;
})
}
}
// Controller
var DashboardController = function ($scope,$dataService) {
$scope.dummyData = [];
activate();
/////////
function activate() {
$dataService.getData()
.then(function(states){
var dataTemp = {};
angular.forEach(states, function (state, key) {
dataTemp[state] = { value: Math.random() }
});
$scope.dummyData = dataTemp;
},function(error) {
console.log(error);
});
}
};
It doesn't matter if the link function of your svgMap directive is called way before you ever make an HTTP get request.
What you have is that the svgMap directive is dynamically creating region directives which are bound to dummyData from the outer controller scope.
Being bound to dummyData means Angular has already registered watchers for the bound property. Literally, this also means that this binding exists even before the properties are actually set on the scope elsewhere, i.e. the parent controller.
I suspect that the $scope.dummyData is not being assigned the returned data from the server properly.
From the code in your question, you are calling the success handler on the get request and getting the response back in the result variable. However, you've not mentioned how you are getting the data from the response. So, I reckon you might have mixed up the response from .then() callback with the success() callback:
In case of the .success() callback:
$http.get('/Account/GetDashboardDetails')
.success(function (result) {
$scope.dummyData = result // data exists in the `result` itself
})
In case of the .then() success callback:
$http.get('/Account/GetDashboardDetails')
.then(function (response) {
$scope.dummyData = response.data; // data is wrapped in `data`
})
If the above is not the case and you're sure enough that the returned data from the server is assigned to the scope properly, then try wrapping that portion of logic inside $timeout(). This triggers $scope.$apply() and ensures that changes in the $scope.dummyData are reflected properly in the view.
var DashboardController = function ($scope, $http, $timeout) {
$http.get('/Account/GetDashboardDetails')
.success(function (result) {
$timeout(function(){
/* Do something here */
$scope.dummyData = result;
});
})

Multiple instances of custom directive, confusion with ngModel

I've created a custom directive to render a slider for a question (essentially wrapping jquery ui slider). The directive takes an ngModel and updates it when the user uses the slider, and there's a $watch attached to the parent model (the ngModel passed to the directive is only a part of a parent model). The directive has multiple instances on a page.
I've encountered an issue with the watch, as it seems as the watch always occurs on the last question on the page. So for example a page with 10 question, using the slider on question 1 - triggers the watch on the last question (question 10). I believe the issue has something to do with directives/isolated scope and/or the watch function, but I'm unable to solve it.
this.app.directive('questionslider', () => {
var onChangeEvent = (event, ui) => {
updateModel(ui.value);
};
var onSlideEvent = (event, ui) => {
updateUi(event, ui);
};
var updateUi = (event, ui) => {
$(ui.handle).find(".ff-handle-glyph > div").css("top", (ui.value) * - 10);
}
var updateModel = (newValue) => {
// find value in values list...
angular.forEach(isolatedScope.model.PossibleValues, function(value) {
if (parseInt(value.Name) === newValue) {
isolatedScope.$apply(function() {
isolatedScope.model.Value = value;
});
}
});
};
var isolatedScope: any;
return {
restrict: 'AE',
replace: true,
template: '<div></div>',
scope: {
model: '=ngModel',
},
link: function(scope, element, attrs, ctrl) {
isolatedScope = scope;
scope.$watch(ngModelCtrl, function() {
// use provided defaultValue if model is empty
var value = isolatedScope.model.Value === null ? isolatedScope.model.DefaultValue : isolatedScope.model.Value;
element.slider({
min: 0,
max: isolatedScope.model.PossibleValues.length,
value: value.Name,
change: onChangeEvent,
slide: onSlideEvent
});
}
}
};
};
Code to add watch in controller
this.$scope.questions.forEach(function(question) {
this.$scope.$watch(
function() { return question; },
function(newVal, oldVal) { this.updateQuestion(newVal, oldVal) },
true
);
});
UpdateQuestion function (right now just outputting current question)
function updateQuestion(newVal, oldVal) {
// prevent event on initial load
if (newVal === oldVal) {
return;
}
console.log(newVal);
}
The ng-repeat markup instantiating questionsliders
<div data-ng-repeat="question in Questions">
<h4>{{question.QuestionText}}</h4>
<p>{{question.RangeMinText}}</p>
<questionslider ng-model="question"></questionslider>
<p>{{question.RangeMaxText}}</p>
</div>
Question JSON would look like this
{
"DefaultValue": {
"Id": "5",
"Name": "5"
},
"Id": "1",
"IsAnswered": false,
"PossibleValues": [
{
"Id": "1",
"Name": "1"
},
{
"Id": "2",
"Name": "2"
},
{
"Id": "3",
"Name": "3"
},
{
"Id": "4",
"Name": "4"
},
{
"Id": "5",
"Name": "5"
},
{
"Id": "6",
"Name": "6"
},
{
"Id": "7",
"Name": "7"
},
{
"Id": "8",
"Name": "8"
},
{
"Id": "9",
"Name": "9"
},
{
"Id": "10",
"Name": "10"
}
],
"QuestionText": "hows it haning?",
"RangeMaxText": "not good",
"RangeMinText": "Very good",
"Type": 0,
"Value": null
}
],
"Title": "Question title",
"Type": 0
}
So issue is, no matter which question I update with the slider directive, it's always the last on page passed into updateQuestion.
UPDATE
I tried using $watchCollection, but nothing seems to fire the event.
this.$scope.$watchCollection(
'questions',
function (newVal, oldVal) {
// prevent event on initial load
if (newVal === oldVal) {
return;
}
for (var i = 0; i < newVal.length; i++) {
if (newVal[i] != oldVal[i]) {
this.$log.info("update : " + newVal.Id);
}
}
}
);
I also tried with
function() { return questions; }
as first expressions. Still no luck.
Maybe using individual controllers for each question is my only option, but it seems a bit of a workaround.
UPDATE
So i tried using individual controllers for each question, adding a watch per question in the controller, and the strange thing is that even this is reproducing same scenario. It's still the last question on the page passed into the watch function.
markup
<div data-ng-repeat="question in Questions" data-ng-controller="QuestionInstanceController">
<h4>{{question.QuestionText}}</h4>
<p>{{question.RangeMinText}}</p>
<questionslider ng-model="question"></questionslider>
<p>{{question.RangeMaxText}}</p>
</div>
Controller code
app.controller('QuestionInstanceController', function($scope) {
console.log($scope.question); // outputs correct question here!
$scope.$watch(
function() { return $scope.question; },
function(newValue, oldValue) {
console.log(newValue); // always outputs last question on page
},
true);
}
}
It must have something to do with my custom directive, am i somehow overwriting previous instances when having multiple instances on the page?
So i managed to find the solution myself. The issue was that i had a "var isolatedScope;" in the directive which was assigned on every run of the link(). I thought vars declared in the directive were isolated on each instance but infact they are overwritten on every implementation of the directive.
The solution therefore was to move the implementation of onChange directly to the init of the slider component, and thus avoiding the need of access to the scope variable later
this.app.directive('questionslider', () => {
var onChangeEvent = (event, ui) => {
};
var onSlideEvent = (event, ui) => {
updateUi(ui.value, ui.handle);
};
var updateUi = (value, element) => {
}
var onStartSlide = (event, ui) => {
}
var onEndSlide = (event, ui) => {
}
return {
restrict: 'AE',
require: 'ngModel',
replace: true,
templateUrl: 'templates/questionSlider.html',
scope: {
model: '=ngModel'
},
link: (scope: any, element, attrs, ngModelCtrl: ng.INgModelController) => {
scope.$watch(ngModelCtrl, () => {
// use provided defaultValue if model is empty
var value = scope.model.Value === null ? scope.model.DefaultValue : scope.model.Value;
var hasNoValue = scope.model.Value === null;
if (hasNoValue) {
element.find('.ui-slider-handle').addClass('dg-notset');
}
element.slider({
min: parseInt(scope.model.PossibleValues[0].Name),
max: scope.model.PossibleValues.length,
value: parseInt(value.Name),
slide: onSlideEvent,
start: onStartSlide,
stop: onEndSlide,
animate: true,
change: (event, ui) => {
// find value in values list...
angular.forEach(scope.model.PossibleValues, (val) => {
if (parseInt(val.Name) === ui.value) {
scope.$apply(() => {
scope.model.Value = val;
});
}
});
onChangeEvent(event, ui);
}
});
});
}
};
});
I expect that your $scope.$watch logic is throwing you off. The simplest way might be to put a watch on the entire array of questions using $watchCollection:
$scope.$watchCollection(questions, function(newValue, oldValue) {
for(index=0; index<newValue.length; index++) {
if (newValue[index] !== oldValue[index]) {
console.log("Question " + index + " updated to: " + newValue[index]);
}
}
});
Otherwise, you could probably create a separate controller for each item in your ng-repeat loop and have a watch there that deals with the change. I don't love this solution, either, as it's kinda long-winded. First, a new controller for dealing with the questions:
app.controller('QuestionCtrl', function($scope) {
$scope.$watch('question', function(newValue, oldValue) {
if (newVal === oldVal) {
return;
}
console.log(newVal);
}
});
With your view slightly modified to include a reference to the new QuestionCtrl:
<div data-ng-repeat="question in Questions" ng-controller='QuestionCtrl'>
<h4>{{question.QuestionText}}</h4>
<p>{{question.RangeMinText}}</p>
<questionslider ng-model="question"></questionslider>
<p>{{question.RangeMaxText}}</p>
</div>
This article gives more information about using controllers with ng-repeat.
I hope one of these helps you out.

Angular modal controller testing error

I have a hard time trying to test a modal controller (created using Angular UI Bootstrap).
I dumbed down the test code as much as I could but I am still getting an error.
Here's the modal controller (part of it):
var controllersModule = angular.module('forge.geomanagement.controllers');
controllersModule.controller('EditGeofenceModalController', function ($timeout, $scope: , $modalInstance, forgeGeoTriggerService, $rootScope, geofence, triggerID) {
var searchAddressInput: HTMLInputElement;
//make a copy of geofence obj passed into modal
$scope.geofence = {
FriendlyName: geofence.FriendlyName,
Coords: angular.copy(geofence.Boundary),
GeoTags: angular.copy(geofence.GeoTags)
};
$scope.goefenceID = triggerID;
var gCLength = $scope.geofence.Coords.length;
//wrap it in timeout function to paint the map after its container is rendered
$timeout(function () {
$scope.geofenceMap = new google.maps.Map(document.getElementById('map_canvas'), $scope.mapOptions);
//autocomplete functionality
searchAddressInput = <HTMLInputElement>document.getElementById('pac-input');
$scope.autocomplete = new google.maps.places.Autocomplete(searchAddressInput, $scope.mapOptions);
$scope.autocomplete.bindTo('bounds', $scope.geofenceMap); //set autocomplete suggestion bounds to map's current viewport
//bind autocomplete to the map
google.maps.event.addListener($scope.autocomplete, 'place_changed', function () {
$scope.place = $scope.autocomplete.getPlace();
$scope.geofenceMap.panTo($scope.place.geometry.location);
$scope.geofenceMap.setZoom(12);
$scope.model.searchAddress = $scope.place.formatted_address;
$scope.$digest();
});
//GEOFENCE FUNCTIONALITY
forgeGeoTriggerService.GeofenceCreator($scope.geofenceMap, $scope.geofence.Coords);
//show geofence in edit mode
forgeGeoTriggerService.ShowGeofence($scope.geofenceMap, $scope.geofence.Coords);
$scope.$on("polygonPath.updated", function (event, geofenceCoords) {
$scope.$apply(function () {
$scope.geofence.Coords = geofenceCoords;
});
});
//clear geofence area btn
$scope.clearGeofenceArea = function () {
forgeGeoTriggerService.ClearGeofenceArea();
$scope.geofence.Coords.length = 0; // clear geofence array
};
}, 0);
$scope.cancel = function () {
$modalInstance.close()
};
$scope.saveGeofence = function () {
forgeGeoTriggerService.EditGeofence($scope.geofence, $scope.goefenceID)
.then(function (data) {
$scope.successMessage = 'Geofence Updated Successfully'
$rootScope.$broadcast('geotrigger.edited');
$timeout(function () {
$modalInstance.close();
}, 2000);
}, function (data) {
$scope.errorMessage = 'There was an error when updating geofence. Please try again.';
});
}
});
This is modal controller test
describe("forge.geomanagement.GeoApp", function () {
var scope, controller, modalInstance, timeout, forgeGeoTriggerService, window = {},
geofencemock, geofence, triggerID;
beforeEach(module('forge.geomanagement.GeoApp'));
describe("Controller: EditGeofenceModalController", function () {
beforeEach(inject(function ($controller, $rootScope, $timeout, _forgeGeoTriggerService_) {
scope = $rootScope.$new();
timeout = $timeout;
modalInstance = {
close: jasmine.createSpy('modalInstance.close'),
dismiss: jasmine.createSpy('modalInstance.dismiss'),
result: {
then: jasmine.createSpy('modalInstance.result.then')
}
}
geofencemock = {
FriendlyName: 'mock geofence',
Coords: [
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
},
{
"lat": 53.463525599115,
"lng": -6.53707981109619
},
{
"lat": 53.3685818160803,
"lng": -6.46841526031494
},
{
"lat": 53.384966558115,
"lng": -5.75430393218994
},
{
"lat": 53.5598889724547,
"lng": -6.34756565093994
},
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
}
],
GeoTags: ['tag1','tag2','tag3']
}
triggerIDmock = 1;
forgeGeoTriggerService = _forgeGeoTriggerService_;
controller = $controller("EditGeofenceModalController", {
$scope: scope,
$timeout: timeout,
$modalInstance: modalInstance,
forgeGeoTriggerService: forgeGeoTriggerService,
geofence: geofencemock,
triggerID: triggerIDmock
});
}));
it('2 is 2', function () {
expect(2).toBe(2);
})
it("geofence should be defined", function () {
expect(geofencemock).toBeDefined();
});
it("should contain reference to forgeGeoTriggerService", function () {
expect(forgeGeoTriggerService).not.toBeNull();
});
it("$modalInstance obj should be defined when modal is open", function () {
expect(modalInstance).toBeDefined();
});
it("cancel function should close edit geofence modal", function () {
scope.cancel();
expect(modalInstance.close).toHaveBeenCalled();
});
});
});
But when I try to run it I get the error: "Cannot read property length of undefined" that corresponds to $scope.geofence.Coords property - an array that is successfully copied over to modal from parent controller. As you can see, I also created a geofencemock object and tried to use it in a very simple test but it looks like it's not being picked up. I would really appreciate some input, cause I have already spent couple of hours trying to fix it or find a solution online, but to no avail.
Thanks.
You're setting $scope.geofence.Coords from geofence.Boundary:
$scope.geofence = {
FriendlyName: geofence.FriendlyName,
Coords: angular.copy(geofence.Boundary),
GeoTags: angular.copy(geofence.GeoTags)
};
But you're mocking geofence with Coords directly:
geofencemock = {
FriendlyName: 'mock geofence',
Coords: [
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
},
Change the latter to be geofencemock.Boundary and you should be fine.
Ok, I got it working. The error "Cannot read property 'lat' of undefined" was related not to the coordinates in geofence mock object but to the geofence.Center.lat property I was using in my controller with geofence.Center.lng to position the center of the map.
Let me explain: we get the polygon details from the server, then we pass them into edit modal window (Angular UI Bootstrap):
forgeGeoTriggerService.GetGeoFence(geotriggerID)
.then(function (geofenceData) {
$scope.modalInstance = $modal.open({
windowClass: 'popup-geofence-modal',
templateUrl: TemplateUrlProvider.GetUrl('GeofenceModal'),
controller: 'EditGeofenceModalController',
resolve: {//pass geofenceData from server to geofence obj inside modal
geofence: function () {
return geofenceData;
},
triggerID: function () {
return geotriggerID
}
}
});
}, function (error) {
$scope.errorMessage = 'There was an error when trying to fetch geofencene details. Please try again later.';
});
Then in EditGeofenceModalController we make use of the geofence object passed from the parent controller above
'use strict';
var controllersModule = angular.module('forge.geomanagement.controllers');
controllersModule.controller('EditGeofenceModalController', function ($timeout, $scope, $modalInstance, forgeGeoTriggerService, $rootScope, geofence, triggerID) {
var searchAddressInput: HTMLInputElement;
//make a copy of geofence obj passed into modal
$scope.geofence = {
FriendlyName: geofence.FriendlyName,
Coords: angular.copy(geofence.Boundary),
GeoTags: angular.copy(geofence.GeoTags)
};
$scope.goefenceID = triggerID;
var gCLength = $scope.geofence.Coords.length;
//if first and last coords are the same - remove the last one
if ($scope.geofence.Coords[0].lat === $scope.geofence.Coords[gCLength - 1].lat
&& $scope.geofence.Coords[0].lng === $scope.geofence.Coords[gCLength - 1].lng) {
$scope.geofence.Coords.pop();
}
//!!!!!!!set the map center to geofence.Center
var geofenceCenter: google.maps.LatLng = new google.maps.LatLng(
geofence.Center.lat, geofence.Center.lng
);
Pay attention to the comment line with exclamation marks. This is where I set the center of the map. The geofence object returned from the server has a Center property - an obj with lat and lng properties. Once I changed the Coords to Boundary in my geofencemock obj in test as #rayners suggested, it was still missing the Center property. Setting it like that in the test file fixed the problem and my tests passed:
geofencemock = {
FriendlyName: 'mock geofence',
Boundary: [
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
},
{
"lat": 53.463525599115,
"lng": -6.53707981109619
},
{
"lat": 53.3685818160803,
"lng": -6.46841526031494
},
{
"lat": 53.384966558115,
"lng": -5.75430393218994
},
{
"lat": 53.5598889724547,
"lng": -6.34756565093994
},
{
"lat": 53.5598889724547,
"lng": -6.36953830718994
}
],
GeoTags: ['tag1', 'tag2', 'tag3'],
Center: {
"lat": 53.46769593973309,
"lng": -6.2952017905716735
}
}

$rootScope.$apply() ignores variable assignment

In my Service I have this code:
setInterval(function () {
$rootScope.$apply(function () {
cart.push({
"Id": 4,
"Name": "Some item",
"Price": 4
});
});
}, 3000);
Working example: http://jsfiddle.net/ko8edf32/7/
This works correctly: the cart change is pushed to the view.
However, when I assign a new value to cart, the change is NOT pushed to the view:
setInterval(function () {
$rootScope.$apply(function () {
var newcart = [{
"Id": 4,
"Name": "Some item " + Math.random(),
"Price": 4
}];
cart = newcart;
});
}, 3000);
example: http://jsfiddle.net/ko8edf32/8/
What is the reason of this?
What is the best/most elegant solution to solve this issue?
EDIT
This is the working solution I've built, based on the answers below:
jsfiddle.net/ko8edf32/11
I've changed your cart into more "angular" way with two-way databinding. I've added the second controller to show you how nice it works all together without getters/setters and generally with a bit of magic
http://plnkr.co/edit/U5pUlAPJTNd9V0baSjAu?p=preview
homeApp.factory("cartService", function($rootScope) {
var service = null
service = {
all: function() {
return service.cart;
},
add: function(item) {
service.total += item.Price
service.cart.push(item);
},
remove: function(item) {
service.total -= item.Price
service.cart.splice(service.cart.indexOf(item), 1);
},
cartUpdated: function(newValue) {
service.cart = newValue;
},
cart: [],
total: 0
}
return service;
});
You have to use objects on $scope.
$scope.model = { cart: [] };
Here is a working example: http://jsfiddle.net/56vkqygn/2/
Here is a explanation: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

grabbing just required results from json --- restangular

I am trying to grab just few ids from json and not the complete payload of json. Is that possible? here is my code
Car.controller('IndexCtrl', function ($scope, CarRestangular) {
$scope.cars = CarRestangular.all('car').getList();
});
here is my json
[
{
"id": 1,
"name": "Mike",
},
{
"id": 2,
"name": "Floyd",
},
{
"id": 3,
"name": "Roy",
},
{
"id": 4,
"name": "Roy",
},
]
I want to get records where ids are 2 and 4. How could I do that. Also if possible, what would I need to do to get top two records in an other case that I would need.
Thanks
The newer versions of angular don't automatically unproxy promises in scope. Therefore you can't immediatelt do:
$scope.cars = CarRestangular.all('car').getList();
But you have to do:
CarRestangular.all('car').getList().then(function(result){
$scope.cars = result;
));
The filtering would be the best if you have lodash included.
https://gist.github.com/lc-nyovchev/7414166e4dbf2dfbc908
You define a service with a method that does the filtering for you:
.factory('SomeService',['$q', 'Restangular', function($q,Restangular){
return {
someMethod: function(){
var deferred = $q.defer();
Restangular.all('car').getList().then(function(results){
var filtered = _.filter(results, function(result){
return result.id === 2 || result.id === 4;
});
return deferred.promise;
}
};
}])
Then you use the service in your controller:
.controller('YourController', ['SomeService', function(SomeService){
SomeService.someMethod().then(function(filteredResult){
$scope.cars = filteredResult;
});
}]);
Alternatively you can just do:
CarRestangular.all('car').getList().then(function(results){
$scope.cars = _.filter(results, function(result){
return result.id === 2 || result.id === 4;
});
});
Edit: all those answers assume you want to do the filtering client side. IF your backend supports it, you can do something like:
CarRestangular.all('car').getList(id:[2,4]).then(function(result){
$scope.cars = result;
));
And if your backend is written in such a way that is supports filtering on the id attribute, it would return you the correct results and you wouldn't have to filter them.

Resources