scope assignment is being automatically updated without being called angularjs - angularjs

I have two scope functions and when i click the button only then will any of them be called but once it is called, i realize that the value of the scope variable automatically updates each time.
$scope.images = [];
$scope.imagesAttached =[];
$scope.takePhoto = function(index) {
if(modalExists === true) {
$scope.modal1.hide();
}
$scope.showSendButton = true;
$scope.attachedImageExists = false;
if($scope.imagesAttached.length > 0) {
$scope.images = $scope.imagesAttached
$scope.attachedImageExists = true;
}
var options = {
destinationType : Camera.DestinationType.FILE_URI,
sourceType : Camera.PictureSourceType.CAMERA,
allowEdit : false,
encodingType: Camera.EncodingType.JPEG,
popoverOptions: CameraPopoverOptions,
correctOrientation: true
};
// 3
$cordovaCamera.getPicture(options).then(function(imageData) {
// 4
var imagetype;
onImageSuccess(imageData);
function onImageSuccess(fileURI) {
createFileEntry(fileURI);
}
function createFileEntry(fileURI) {
window.resolveLocalFileSystemURL(fileURI, copyFile, fail);
}
// 5
function copyFile(fileEntry) {
var name = fileEntry.fullPath.substr(fileEntry.fullPath.lastIndexOf('/') + 1);
var newName = (new Date()).getTime() + name;
window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(fileSystem2) {
fileEntry.copyTo(
fileSystem2,
newName,
onCopySuccess,
fail
);
}, fail);
}
// 6
function onCopySuccess(entry) {
$scope.$apply(function() {
$scope.modal.remove();
$scope.activeSlide = index;
if(modalExists === false) {
$ionicModal.fromTemplateUrl('image-modal.html', {
scope: $scope,
}).then(function(modal) {
$scope.modal1 = modal;
$scope.modal1.show();
modalExists = true;
$scope.images.push({file: entry.nativeURL, type: $scope.imagelist});
console.log($scope.imagesAttached.length)
console.log($scope.images.length)
});
}
else {
$scope.modal1.show();
$scope.images.push({file: entry.nativeURL, type: $scope.imagelist});
console.log($scope.imagesAttached.length)
console.log($scope.images.length)
}
});
}
function fail(error) {
console.log("fail: " + error.code);
}
}, function(err) {
console.log(err);
});
}
$scope.sendPhoto = function() {
$scope.imagesAttached = angular.copy($scope.images);
}
my image-modal.html page
<script id="image-modal.html" type="text/ng-template">
<div class="modal image-modal transparent">
<ion-header-bar class="bar bar-header bar-dark">
<div class ="row">
<div class ="col">
<label ng-click="closeModal(1)">Cancel</label>
</div>
<div class ="col">
<label ng-click="deleteImage()">Delete</label>
</div>
<div class ="col" id="send-images" ng-show="showSendButton">
<label ng-click="sendtoAttach()">Send</label>
</div>
</div>
</ion-header-bar>
<ion-slide-box on-slide-changed="slideChanged($index)" show-pager="true" active-slide="activeSlide" >
<ion-slide ng-repeat="image in images">
<img ng-src="{{image.file}}" class="fullscreen-image"/>
</ion-slide>
</ion-slide-box>
<div class="row">
<ion-scroll>
<img ng-repeat="image in images" ng-src="{{urlForImage(image.file)}}" class="image-list-thumb" height="50px"/>
</ion-scroll>
<button class="ion-camera" ng-click="takePhoto()"></button>
</div>
</div>
</script>
I have got two buttons Take and Send
when i call takePhoto for the first time and SendPhoto, the value is correct, one image is pushed and the length of my $scope.images and $scope.imagesAttached is 1,
But if i click takePhoto button again, without calling SendPhoto button, both my $scope.images and $scope.imagesAttached length is updated to 2 whereas it should be only $scope.images = 2 while $scope.imagesAttached = 1 since i havent called $scope.sendPhoto yet.
I know angularJS has some double binding stuff with $apply and $digest but not sure how it works and why it is auto binding my scope variables.
Any help appreciated

This has nothing to do with Angular, it is purely JavaScript object references at work.
When you assign $scope.images to $scope.imagesAttached, both variables reference the same object.
Try this instead in your sendPhoto function
$scope.imagesAttached = angular.copy($scope.images)

Related

Angular ion-radio filter data from external db

I have 2 tables in Azure db 1 called team and 1 called league, the id in league is also associated to a team e.g. team a belongs to leagueID 1. I am trying to use ion-radio so users can choose their team but filter it by the league but cannot work out whether ng-if or filter is the best option. I have set up a service to call the data from azure: services.js
.factory('League', function ($ionicPopup) {
var url = 'http://';
var client = new WindowsAzure.MobileServiceClient(url);
var LeagueTable = client.getTable('League');
function refreshDisplay() {
return LeagueTable
.read()
.then(createList, handleError);
}
function createList(items) {
return items;
}
function handleError(error) {
var LeagueName = error + (error.request ? ' - ' + error.request.status : '');
console.error(LeagueName);
console.log('error', error.request.status);
if (error.request.status == '0' || error.request.status == '404') {
$ionicPopup.alert({
title: 'Connection Failure',
template: 'Connection with backend can not be established.'
});
}
}
return {
all: function () {
return refreshDisplay();
},
};
})
.factory('Team', function ($ionicPopup) {
var url = 'http://';
var client = new WindowsAzure.MobileServiceClient(url);
var TeamTable = client.getTable('Team');
function refreshDisplay() {
return TeamTable
.read()
.then(createTeamList, handleError);
}
function createTeamList(items) {
return items;
}
function handleError(error) {
var text = error + (error.request ? ' - ' + error.request.status : '');
console.error(text);
console.log('error', error.request.status);
if (error.request.status == '0' || error.request.status == '404') {
$ionicPopup.alert({
title: 'Connection Failure',
template: 'Connection with backend can not be established.'
});
}
}
return {
all: function () {
return refreshDisplay();
},
}
});
The controller at the moment just pulls back all the teams, controller.js:
.controller('LeagueCtrl', function ($scope, $window, League) {
$scope.doRefresh = function () {
League.all().then(function (newList) {
$scope.items = newList;
$scope.$broadcast('scroll.refreshComplete');
$scope.$apply();
});
};
$scope.doRefresh();
console.log(League);
$scope.League;
})
.controller('TeamCtrl', function ($scope, $window, Team) {
$scope.doRefresh = function () {
Team.all().then(function (newList) {
$scope.items = newList;
$scope.$broadcast('scroll.refreshComplete');
$scope.$apply();
return $scope.items
});
};
$scope.doRefresh();
});
And the HTML teams.html:
<ion-view view-title="Choose your Team">
<ion-nav-buttons side="primary">
</ion-nav-buttons>
<ion-content>
<ion-refresher pulling-text="Pull to refresh..."
on-refresh="doRefresh()">
</ion-refresher>
<div ng-controller="SportCtrl">
<h2> Choose sport</h2>
<ion-radio ng-model="item.complete" ng-repeat="item in items" > {{item.text}}</ion-radio>
</div>
<div ng-controller="LeagueCtrl">
<h2> Choose Conference</h2>
<ion-radio ng-model="item.complete" ng-repeat="item in items"> {{item.LeagueName}}</ion-radio>
</div>
<div ng-controller="TeamCtrl">
<h2> Choose Team</h2>
<ion-radio ng-model="item.complete" ng-repeat="item in items"> {{item.TeamName}}</ion-radio>
</div>
<div >
<button class="button button-large-font-size button-block button- assertive" ui-sref="app.home">Booyah</button>
</div>
</ion-content>
</ion-view>
Can anyone point me in the right direction
As the scopes in different controller are separate, so there is a simple solution, you can put the League and Team in a same controller and in a same scope, then you can custom a filter function in your controller to achieve your requirements.
Generally:
<div ng-controller="LeagueTeamCtrl">
<h2> Choose Conference</h2>
<ion-radio ng-model="data.league_id" ng-repeat="item in league" ng-value="item.id">{{item.LeagueName}}</ion-radio>
<h2> Choose Team</h2>
<ion-radio ng-model="data.team_id" ng-repeat="item in team" ng-value="item.id">{{item.TeamName}}</ion-radio>
</div>
And the custom filter function:
$scope.myFilter = function (item) {
return item.leagueId === $scope.league_id ;
};
And you can refer to the full example at http://codepen.io/liuguixiao/pen/ZpYLAB

How to empty md-autocomplete text field after submit?

I used md-autocomplete inside html form, then I wanted to set its field to empty after submitting. I tried, as shown in the snippet, to use $setPristine() but it didn't work. I also tried to assign the model to null or empty string, but also it wasn't successful.
PS: Angularjs version I used is v1.3.15
angular.module("myApp", ["ngMaterial"])
.controller("main", function($scope){
$scope.searchString = "";
$scope.routeToSearchPage = function (searchString, form) {
$scope.form.$setPristine();
};
$scope.simulateQuery = false;
$scope.isDisabled = false;
$scope.states = loadAll();
$scope.querySearch = querySearch;
function querySearch (query) {
var results = query ? $scope.states.filter( createFilterFor(query) ) : $scope.states,
deferred;
if ($scope.simulateQuery) {
deferred = $q.defer();
$timeout(function () { deferred.resolve( results ); }, Math.random() * 1000, false);
return deferred.promise;
} else {
return results;
}
}
function loadAll() {
var allStates = "aaa, bbbb, cccc, bla";
return allStates.split(/, +/g).map( function (state) {
return {
value: state.toLowerCase(),
display: state
};
});
}
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(state) {
return (state.value.indexOf(lowercaseQuery) === 0);
};
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.7/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.7/angular-animate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.7/angular-aria.js"></script>
<script src="//rawgit.com/angular/bower-material/master/angular-material.js"></script>
<div ng-app="myApp">
<div ng-controller="main">
<form name="form"
ng-submit="submit(searchString);$event.preventDefault();">
<md-autocomplete
md-floating-label="search"
ng-disabled="isDisabled"
md-no-cache="noCache"
md-selected-item="selectedItem"
md-search-text="searchString"
md-items="item in querySearch(searchString)"
md-item-text="item.display"
ng-keyup="$event.keyCode == 13 ? routeToSearchPage(searchString) : null">
<md-item-template>
<span md-highlight-text="searchString" md-highlight-flags="^i">{{item.display}}</span>
</md-item-template>
</md-autocomplete>
</form>
<md-button ng-click="routeToSearchPage(searchString, form)">
<md-icon class="material-icons">search</md-icon>
</md-button>
</div>
</div>
Here's an example of clearing an md-autocomplete - CodePen
Your snippet isn't actually showing the md-autocomplete as it should appear.
JS
$scope.clear = function () {
$scope.searchString = null;
};
I have also removed the markup below for the CodePen to work correctly:
ng-keyup="$event.keyCode == 13 ? routeToSearchPage(searchString) : null">

md-checkbox does not work with ng-click

I want to save position each time when I change checkbox:
<h1 class="md-display-2">Simple TODO ng app</h1>
<h2 class="md-display-3"></h2>
<div ng-include src="'todo/add.html'"></div>
<div>
<div layout="row">
<div flex class="md-title">Scope</div>
<div flex="10" class="md-title">Till date</div>
<div flex="10" class="md-title">Is reached?</div>
<div flex="10" class="md-title">
<span ng-click="todoctrl.show_add()" class="material-icons controls">add</span>
</div>
</div>
<div layout="row" ng-repeat="todo in todoctrl.todos track by $index">
<div flex ng-class="{true:'striked', false:'simple'}[todo.reached]">{{todo.name}}</div>
<div flex="10">
{{todo.tillDate | date:'dd/MM/yyyy'}}
</div>
<div flex="10">
<md-checkbox ng-model="todo.reached" aria-label="Is reached" ng-click="todoctrl.changeState(todo.name)"></md-checkbox>
</div>
<div flex="10">
<span ng-click="todoctrl.deleteScope(todo.name)"
class="material-icons controls">clear</span>
</div>
</div>
</div>
In this case controller is touched (I tried with to debug with console log), but the checkbox value is not changed before page reload. After reload the value is checkbox is presented as expected.
If I remove ng-click="todoctrl.changeState(todo.name)" then checkbox is working good, but no info is sent to controller.
This is my service:
(function() {
'use strict';
angular.module('app').service('ToDoService', ToDoService);
ToDoService.$inject = ['JsonService'];
function ToDoService(JsonService) {
return {
deleteScope : deleteScope,
submitScope : submitScope,
changeState : changeState,
getData : getData
}
function getData() {
var todos = JsonService.getData();
return todos;
}
function deleteScope(arr, scope) {
arr.splice(findElementByScope(arr, scope), 1);
JsonService.setData(arr);
}
function submitScope(arr, scope, tillDate) {
var newTodo = {};
newTodo.name = scope;
newTodo.reached = false;
newTodo.tillDate = tillDate;
arr.push(newTodo);
JsonService.setData(arr);
}
function changeState(arr, scope) {
console.log("Service change state for scope: " + scope);
var todo = {};
var index = findElementByScope(arr, scope);
todo = arr[index];
todo.reached = !todo.reached;
JsonService.setData(arr);
}
function findElementByScope(arr, scope) {
for (var i = arr.length; i--;) {
if (arr[i].name == scope) {
return i;
}
}
return -1;
}
}
})();
And this is the Controller:
(function() {
'use strict';
angular.module('app').controller('ToDoController', ToDoController);
function ToDoController(ToDoService) {
var vm = this;
vm.show_form = false;
vm.todos = ToDoService.getData();
vm.scope = '';
vm.show_add = show_add;
vm.submitScope = submitScope;
vm.deleteScope = deleteScope;
vm.changeState = changeState;
function show_add() {
console.log("Controller show add");
vm.show_form = true;
}
function submitScope() {
ToDoService.submitScope(vm.todos, vm.scope, vm.tillDate);
vm.show_form = false;
vm.scope = '';
}
function deleteScope(scope) {
ToDoService.deleteScope(vm.todos, scope);
}
function changeState(scope) {
ToDoService.changeState(vm.todos, scope);
}
}
})();
Use ng-change instead of ng-click
<md-checkbox ng-model="todo.reached" aria-label="Is reached" ng-change="todoctrl.changeState(todo.name, todo.reached)"></md-checkbox>
ng-change trigger after value change in model

$digest already in progress when i use ionicplatform.ready with camera functionality

This is my controller, i am trying to capture image with a button in page 1 and store it locally, and display image1 in page 1, then if i click the button again to take the pic of image2. Image2 should be displayed in page1 and image1 should be viewed in page2
.controller('LeadDetailController', [
'$scope',
'$cordovaDevice',
'$cordovaFile',
'$ionicPlatform',
'ImageService', 'FileService',
function( $scope,
$cordovaDevice,
$cordovaFile,
$ionicPlatform,
ImageService, FileService) {
// image capture code
$ionicPlatform.ready(function() {
console.log('ionic is ready');
$scope.images = FileService.images();
$scope.$apply();
});
$scope.urlForImage = function(imageName) {
var trueOrigin = cordova.file.dataDirectory + imageName;
return trueOrigin;
}
$scope.addImage = function(type) {
ImageService.handleMediaDialog(type).then(function() {
$scope.$apply();
});
}
at the initial stage itself i am getting this error
Error: [$rootScope:inprog] $digest already in progress
page1 with buttons
// here camera function is called to open the camera and take pic
<ion-option-button ng-click="addImage()"class="icon ion-android-camera"></ion-option-button>
//here the pic taken in camera should be displayed
<ion-option-button>
<img src="">
</ion-option-button>
//here moveing to the next page2
<ion-option-button ng-click="Page2()" class="icon ion-ios-grid-view"></ion-option-button>
page2 html
<ion-view>
<ion-nav-bar class="bar-positive">
<ion-nav-title class="title">Grid View</ion-nav-title>
</ion-nav-bar>
<ion-content class="has-header">
<img ng-repeat="image in images" ng-src="{{image.src}}" ng-click="showImages($index)" style="height:50%; width:50%; padding:2px ">
</ion-content>
</ion-view>
page2 controller
.controller('gridController', function($scope, $ionicBackdrop, $ionicModal, $ionicSlideBoxDelegate, $ionicScrollDelegate) {
//here the images are stored inside the array
$scope.images = [{ }];
services
.factory('FileService', function() {
var images;
var IMAGE_STORAGE_KEY = 'images';
function getImages() {
var img = window.localStorage.getItem(IMAGE_STORAGE_KEY);
if (img) {
images = JSON.parse(img);
} else {
images = [];
}
return images;
};
function addImage(img) {
images.push(img);
window.localStorage.setItem(IMAGE_STORAGE_KEY, JSON.stringify(images));
};
return {
storeImage: addImage,
images: getImages
}
})
.factory('ImageService', function($cordovaCamera, FileService, $q, $cordovaFile) {
function makeid() {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 5; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
function optionsForType(type) {
var source;
/* switch (type) {
case 0:
source = Camera.PictureSourceType.CAMERA;
break;
case 1:
source = Camera.PictureSourceType.PHOTOLIBRARY;
break;
}*/
return {
destinationType: Camera.DestinationType.FILE_URI,
sourceType: source,
allowEdit: false,
encodingType: Camera.EncodingType.JPEG,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
}
function saveMedia(type) {
return $q(function(resolve, reject) {
var options = optionsForType(type);
$cordovaCamera.getPicture(options).then(function(imageUrl) {
var name = imageUrl.substr(imageUrl.lastIndexOf('/') + 1);
var namePath = imageUrl.substr(0, imageUrl.lastIndexOf('/') + 1);
var newName = makeid() + name;
$cordovaFile.copyFile(namePath, name, cordova.file.dataDirectory, newName)
.then(function(info) {
FileService.storeImage(newName);
resolve();
}, function(e) {
reject();
});
});
})
}
return {
handleMediaDialog: saveMedia
}
});
could someone help me to fix this issue and to help me with page2 to imageviewing
As stated by #Ila the problem is likely due to $scope.$apply().
So if you can't predict if it has to be used then insert in an if statement as follows:
if(!$scope.$$phase) {
// no digest in progress...
$scope.$apply();
}
However this is not a good practice: prefer to use $scope.$apply() only when really needed (). See Angular docs
I agree with vb-platform. But what you can also do is wrapping your code into a $timeout so angularjs would handle the $apply for you ($timeout):
$ionicPlatform.ready(function() {
console.log('ionic is ready');
$timeout(function setImages() {
$scope.images = FileService.images();
});
});

AngularJS - ng-if runs digest loop

I am facing problem with infinite loop on loading the view. The data is loaded from an API call using ngResource in the controller. The view seems to be reloaded multiple times before rendering the view correctly. I use ng directives in the template calling scope methods and this seems to get into loop causing the view to be re-rendered.
Here is my Controller
.controller('IndexCtrl', ['$scope', '$stateParams', 'ProfileInfo',
function($scope, $stateParams, ProfileInfo) {
$scope.navTitle = 'Profile Information';
$scope.data = {};
ProfileInfo.query({
id: $stateParams.id
}).$promise.then(function(Profile) {
if (Profile.status == 200) {
$scope.data.Profile = Profile.data[0];
}else{
console.log(Profile.status);
}
}, function(error) {
console.log(error);
});
$scope.showImageBlock = function(object, image) {
if (object.hasOwnProperty('type') && object.type == 'image') {
imageReference = object.value;
var imageUrl;
angular.forEach(image, function(value, key) {
if (value.id == imageReference) {
$scope.data.imageUrl = value.graphic.url;
return;
}
});
}
return object.hasOwnProperty('type') && object.type == 'image';
};
$scope.showText = function(object) {
console.log('text');
return object.hasOwnProperty('type') && object.type == 'text';
};
}
])
And Here is my template
<ion-view cache-view="false">
<ion-nav-title>
{{navTitle}}
</ion-nav-title>
<div class="bar bar-subheader bar-light">
<h2 class="title">{{navSubTitle}}</h2>
</div>
<ion-content has-header="true" padding="true" has-tabs="true" class="has-subheader">
<div ng-repeat="profileInfo in data.Profile">
<div class="list">
<img ng-if="showImageBlock(profileInfo,data.Profile.images)" ng-src="{{ data.imageUrl }}" class="image-list-thumb" />
<div ng-if="showText(profileInfo)">
<a class="item">
{{profileInfo.name}}
<span ng-if="profileInfo.description.length != 0"class="item-note">
{{profileInfo.description}}
</span>
</a>
</div>
</div>
</div>
</ion-content>
Here is the output of console window when tried log the number of times showText function is called.
The actual result from ngResource call has only 9 items in array but it loops more than 9 times and also multiple loops. This happens for a while and stops. Could anyone please point me in the right direction in fixing it.
Thank you
Finally I ended up creating a custom directive which does the function of ng-if without the watchers which triggers the digest loop. It's not a pretty solution but it seems to do the job as expected. I copied the code of ng-if and removed the $scope watcher. Here is the custom directive.
angular.module('custom.directives', [])
.directive('customIf', ['$animate',function($animate) {
return {
multiElement: true,
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
$$tlb: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
value = $scope.$eval($attr.customIf);
if (value) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end customIf: ' + $attr.customIf + ' ');
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
}
else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements).then(function() {
previousElements = null;
});
block = null;
}
}
}
};
}]);
This allows us to use the customIf as follows
<div custom-if="showText(profileInfo)">

Resources