I am writing a small AngularJS/Ionic/Cordova application where I have a list of contacts. When the user taps on a contact, I navigate to a new page where details about the contact are shown (first name, last name, phone number). Here, the user can update details about the contact, or delete the contact. The problem I have is when the user deletes the contact the list still shows the deleted item after navigating back.
Right now I am storing the list of contacts in localStorage, but I do plan on eventually persisting these values in SQLite and/or a web service.
I am using two controllers, two html templates, and a factory. Why is the first list not updating when I make changes to a detail item? I am very new to AngularJS so please bear with me.
List Controller
angular
.module('app')
.controller('contactListCtrl', ['$scope', 'Contacts',
function($scope, Contacts) {
$scope.contacts = Contacts.get();
}
])
List Template
<ion-content scroll="false" class="padding-horizontal" has-header="true">
<div class="row padding-vertical">
<div class="col col-center text-center">
<span class="small-text primary-text-color">Contact List</span>
</div>
</div>
<div class="row" >
<div class="col col-center">
<ul class="list">
<li class="item" ng-repeat="contact in contacts" ui-sref="contactListEdit({id: $index})">
{{contact.fName}} {{contact.lName}}
</li>
</ul>
</div>
</div>
</ion-content>
Detail Controller
angular
.module('app')
.controller('editContactCtrl', ['$scope', '$stateParams', 'Contacts',
function($scope, $stateParams, Contacts) {
var contactId = false;
if ($stateParams.id !== 'undefined') {
contactId = $stateParams.id;
}
$scope.contact = Contacts.get(contactId);
$scope.contactId = contactId;
$scope.delete = function(index) {
Contacts.removeContact(index);
window.history.back();
};
}
])
Detail Template
<ion-content scroll="false" class="padding-horizontal" has-header="true">
<div class="row padding-vertical">
<div class="col col-center text-center">
<span class="medium-text primary-text-color">Edit contact</span>
</div>
</div>
<div class="list">
<label class="item item-input">
<input type="text" placeholder="First Name" ng-model="contact.fName">
</label>
<label class="item item-input">
<input type="text" placeholder="Last Name" ng-model="contact.lName">
</label>
<label class="item item-input">
<input type="text" placeholder="Phone" ng-model="contact.mobile">
</label>
<label class="item item-input">
<input type="text" placeholder="Email" ng-model="contact.email">
</label>
<button ng-click="save()" class="button button-full button-positive">
Save
</button>
<button ng-click="delete(contactId)" class="button button-full button-assertive">
Delete
</button>
</div>
</ion-content>
Contacts Factory
angular
.module('app')
.factory('Contacts', ['$http',
function($http) {
return {
get: function(index) {
var contacts;
try {
contacts = JSON.parse(window.localStorage.getItem("contacts"));
if (index >= 0) {
return contacts[index];
}
return contacts;
} catch (e) {
console.log("error parsing json contacts");
return false;
}
},
removeContact: function (index) {
var contactList = this.get();
if (index !== -1) {
contactList.splice(index, 1);
}
console.log(contactList);
this.saveContacts(contactList);
},
setDefaultContacts: function() {
var self = this;
return $http.get('./mock_data/contacts.json').then(function(response) {
// contacts already exist, don't set.
if (window.localStorage.getItem("contacts")) {
return false;
} else {
self.saveContacts(response.data);
return true;
}
});
},
saveContacts: function(contactList) {
window.localStorage.setItem("contacts", JSON.stringify(contactList));
},
};
}
])
I think you will find that the contactListCtrl is not being setup again when you navigate back to it (after a delete). I believe this is because Ionic caches views for you.
What you need to do is listen to $ionicView.beforeEnter event and load your contacts list into your ContactListCtrl scope whenever you receive this event.
So.. Try adding something like this to your controller:
$scope.$on('$ionicView.beforeEnter', function() {
$scope.contacts = Contacts.get();
}
);
Check out: http://ionicframework.com/blog/navigating-the-changes/
Related
I am using angularjs and spring mvc for my application. At first I am displaying all the placements with one button Placed Candidates. I am doing ng-repeat for displaying all the placements.
Here is my html.
<div class="row" data-ng-repeat="placement in placements">
<div class="col-xs-12 col-sm-12 col-md-12">
<h5>{{placement.companyName}}</h5>
</div>
<div class="col-xs-12 col-sm-6 col-md-3">
<h6>{{placement.placementDate | date:'dd MMM yyyy'}}</h6>
Company Target (appox):10 Achieved:
</div>
<a href="javascript:void(0);" data-ng-
click="placedcandidatespopup(placement.placementId);">//here i can
get particular placementId but how to pass this id to
savePlacementStudents() method in controller.js???//
PlacedCandidates
</a>
</div>
I am calling one modal popup function. Here is controller.js
$scope.placedcandidatespopup = function()
{
AdminUsersService.getAllUsers().then(function(response)
{
$scope.allusers=response.data;
console.log($scope.allusers);
})
$(".placedcandidates").modal("show");
}
I am calling modal by name "placedcandidates".
Here is the modal
<div class="modal right fade placedcandidates" id="myModal1"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel2">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6" data-ng-
repeat="student in allusers">
<input id="{{student.studentId}}" type="checkbox" value="
{{student.studentId}}" data-ng-
checked="selection.indexOf(student.studentId) >-1"
data-ng-click="toggleSelection(student.studentId)"/>
<label for="{{student.studentId}}"></label>
{{student.firstName}}
</div>
</div>
</div>
<div class="modal-footer" style="position: fixed;">
<input type="button" value="Submit" class="btn" data-ng-
click="savePlacementStudents()">
</div>
</div>
</div>
</div>
After clicking on button popup modal will display with all Students with checkboxes placed beside them.Now I am able to save studentId into database.But I am not able to pass placementId for saving.
Here is my savePlacementStudents method in controller.js.
$scope.selectedStudents = {};
$scope.savePlacementStudents = function(selectedStudents)
{
selectedStudents.selectedList = $scope.selection;
AdminPlacementService.savePlacementStudents(selectedStudents).then
(function(response)
{
if(response.data=="success"){
$window.scrollTo(0,0);
$scope.selectedStudents = {};
$scope.getplacementdetails($scope.currentPageIndex);
$scope.successmessage="Student Selection Saved!;
$timeout(function() {
$scope.successmessage="";
$scope.closeplacedcandidatespopup();
}, 1500);
}
});
}
Can anyone tell how can I pass respective placementId to this saveStudents method() so that I can set that placementId for those students selected.
Here is the solution.
The placementId passed to function placedcandidatespopup can be saved as follows in function placedcandidatespopup
$scope.selectedPlacementId = placementId;
Now the same $scope will have access in Popup also as it is using same controller.js. And now you can use this $scope.selectedPlacementId anywhere in controller.js
So you don't need to pass placementId...!!
Hope that would help you..!!
$scope.placedcandidatespopup = function(id)
{
AdminUsersService.getAllUsers().then(function(response)
{
$scope.allusers=response.data;
console.log($scope.allusers);
})
$scope.inserted = {
placementId:id,
};
var modalInstance = $uibModal.open({
templateUrl: 'views/test/myModal1.html',
controller: 'TestCntrl',
size: "Modelwidth",
resolve: {
items: function () {
return $scope.inserted;
}
}
});
}
In model you have to access using items.placementId and when you savePlacementStudents() method send placementId as well with student info.
I have two drop downs with add resource button, when I click add resource button I need to pass selected option values from drop downs into insertResource method.How to get selected option value??I know we can easily do it using option:selected in jquery But I want do it in angular.Any help?
<body ng-app="intranet_App" ng-controller="myCtrl" ng-init="init()">
<div class="container">
<div class="row">
<div>
<label class="displayBlock margin">Project Name</label>
<input type="text" name="name" class="currentProjectName">
</div>
<div>
<label class="displayBlock margin">Resource Name</label>
<select name="ResourceInsert" id="allocateResource"><option data-ng-repeat="data in resourceList" value="{{data.EmpId}}">{{data.ResourceName}}</option></select>
</div>
<div>
<label class="displayBlock margin">Role Name</label>
<select name="ResourceInsert" id="allocateRole"><option data-ng-repeat="data in roleList" value="{{data.RoleId}}">{{data.RoleName}}</option></select>
</div>
</div>
<div class="row">
<button class="btn btn-primary addResource" ng-click="insertResource()">Add Resource</button>
</div>
</div>
</body>
<script>
var app = angular
.module('intranet_App', [])
.controller('myCtrl', function ($scope,$http) {
$scope.init = function () {
$scope.getProjId();
$scope.ResourceJson();
$scope.RoleJson();
}
$scope.getProjId = function () {
var url = document.URL;
var id = decodeURI(/id=([^&]+)/.exec(url)[1]);
var projectName = decodeURI(/val=([^&]+)/.exec(url)[1]);
$('.currentProjectName').val(projectName)
}
$scope.ResourceJson = function () {
$http.post('/Project/empList').then(function (response) {
$scope.resourceList = response.data;
console.log($scope.resourceList)
})
}
$scope.RoleJson = function () {
$http.post('/Project/roleList').then(function (response) {
$scope.roleList = response.data;
console.log($scope.roleList)
})
}
$scope.insertResource = function () {
}
});
</script>
If your questions is getting data of selected item of select. It is done as follows using ng-model directive:
<select name="ResourceInsert" id="allocateResource" ng-model="selectedValue">
<option data-ng-repeat="data in resourceList" value="{{data.EmpId}}">{{data.ResourceName}}</option>
</select>
In Controller:
console.log($scope.selectedValue, "selected Value"); //Your selected value which is EmpId.
I have 2 buttons:
One scans a QR code and returns the values which works.
Another on the same page that confirms the input values, but I cannot get the second function to fire.
I have 2 inputs, deviceid and companyid, and 2 buttons. The scan button, via ng-click, calls scanQR and the second button, also via ng-click, calls checkBlanks.
A confirm ID button calls a functions to firstly check for blanks then alert the user if the input data is missing. Once the 2 inputs have content it will save the data via the verifyDetails() call. But I cannot get the checkBlanks function to run.
HTML Code is:-
<ion-view title="Settings">
<ion-content padding="true" class="has-header">
<form class="list">
<label class="item item-input" name="deviceid">
<span class="input-label">Device ID</span>
<input type="text" placeholder="" ng-model="deviceid">
</label>
<label class="item item-input" name="companyid">
<span class="input-label">Company ID</span>
<input type="text" placeholder="" ng-model="companyid">
</label>
</form>
<button ng-click="scanQR()" class="button button-positive button-block">Scan QR to Configure</button>
<button ng-click="checkBlanks()" class="button button-positive button-block">Confirm ID</button>
<form class="list">
<ion-toggle toggle-class="toggle-positive" ng-checked="true">Vibrate</ion-toggle>
<ion-toggle toggle-class="toggle-positive">Light on Scan</ion-toggle>
<div style="text-align:center;" class="show-list-numbers-and-dots">
<p style="color:#000000;font-size:10px;">On Android devices set to off and use the volume controls to turn the flashlight on or off.</p>
</div>
<ion-radio ng-model="radioCheck" value="security">Security</ion-radio>
<ion-radio value="utilities">Utilities</ion-radio>
</form>
</ion-content>
</ion-view>
AngularJS is:-
function($scope, $stateParams) {
$scope.radioCheck = "security";
$scope.scanQR = function() {
cordova.plugins.barcodeScanner.scan(
function(result) {
var TagResponse = result.text;
var config = TagResponse.indexOf("Config:");
if (TagResponse !== '') {
if (config === 0) {
var ConfigVals = TagResponse.split(':');
$scope.deviceid = ConfigVals[1];
$scope.companyid = ConfigVals[2];
$scope.$apply();
}
}
});
};
$scope.checkBlanks = function() {
alert("Device ID" + $scope.deviceid);
console.log("Device ID" + $scope.device)
if ($scope.deviceid === '') {
alert("Please enter a device ID", Dismissed, "Error");
} else if ($scope.companyid === '') {
alert("Please enter a Company ID", Dismissed, "Error");
} else {
verifyDetails();
}
};
}
Any help appreciated, I'm very new to Angularjs and Ionic.
My functionality is very simple, and I have to imagine very common. I have a page that lists a collection of 'age groups', and provides functionality to add an 'age group'. I simply would like to have the page immediately reflect the change when a new 'age group' is added.
In early versions, I had simply used the $rootScope in my service to put all 'age groups' in the $rootScope when the $http request was completed. On this latest refactoring I am removing all use of $rootScope in my app, and it is becoming problematic.
The code for the HTML view is as follows: [NOTE: all code considered extraneous has been removed from all code snippets]
<div id="tabContent" class="tab-pane fade in active" ng-show="subIsSelected(1)">
<div class="row">
<div class="col-xs-12">
<form class="form-inline">
<button type="button" class="btn btn-success" ng-click="openAddAgeGroup()">Add Age Group</button>
</form>
</div>
</div>
<hr />
<div class="row row-content">
<div class="col-xs-12">
<h4 ng-show="!areAgeGroups()">No current age groups.</h4>
<ul class="media-list" ng-show="areAgeGroups()">
<li class="media" ng-repeat="ageGroup in ageGroups" style="padding:10px; background: lightGray;">
<div class="media-left media-top" >
<img class="media-object img-thumbnail" style="width: 75px;" ng-src="./images/trfc.png" alt="club logo">
</div>
<div class="media-body">
<h3 class="media-heading" style="padding-top: 20px;">{{ageGroup.name}}</h3>
<button type="button" class="btn btn-xs btn-primary" style="width: 50px;" ng-click="openEditAgeGroup(ageGroup)">Edit</button>
<button type="button" class="btn btn-xs btn-danger" style="width: 50px;" ng-click="deleteAgeGroup(ageGroup)">Delete</button>
</div>
</li>
</ul>
</div>
</div>
</div>
when first loaded, the page correctly shows all 'age groups' that are in the $scope.ageGroups array.
On clicking the button to add an 'age group', an ng-dialog is created as follows:
$scope.openAddAgeGroup = function() {
console.log("\n\nOpening dialog to add age group");
ngDialog.open({ template: 'views/addAgeGroup.html', scope: $scope, className: 'ngdialog-theme-default', controller:"HomeController" });
};
and that dialog is populated as such:
<div class="ngdialog-message">
<div class="">
<h3>Add a New Age Group</h3>
</div>
<div> </div>
<div>
<form ng-submit="addAgeGroup()">
<div class="form-group">
<label class="sr-only" for="name">Age Group Display Name</label>
<input type="text" class="form-control" id="name" placeholder="age group name" ng-model="ageGroupForm.name">
</div>
<div class="form-group">
<label class="sr-only" for="birthyear">Birth Year</label>
<input type="text" class="form-control" id="birthyear" placeholder="birth year" ng-model="ageGroupForm.birthyear">
</div>
<div class="form-group">
<label class="sr-only" for="socceryear">Soccer Year</label>
<div class="input-group">
<div class="input-group-addon">U</div>
<input type="text" class="form-control" id="socceryear" placeholder="soccer year" ng-model="ageGroupForm.socceryear">
</div>
</div>
<button type="submit" class="btn btn-info">Add</button>
<button type="button" class="btn btn-default" ng-click=closeThisDialog("Cancel")>Cancel</button>
</form>
</div>
</div>
When the form is submitted, the 'age group' is added to the database, from the controller:
'use strict';
angular.module('ma-app')
.controller('HomeController', ['$scope', 'ngDialog', '$state', 'authService', 'coreDataService', 'userService', '$rootScope', 'clubService', 'schedulingService', function($scope, ngDialog, $state, authService, coreDataService, userService, $rootScope, clubService, schedulingService) {
...
$scope.addAgeGroup = function() {
coreDataService.addAgeGroup($scope.ageGroupForm)
.then(function(response) {
coreDataService.refreshAgeGroups()
.then(function(response) {
coreDataService.setAgeGroups(response.data);
$scope.ageGroups = coreDataService.getAgeGroups();
console.log("\n\n\nretrieved age groups and put them in scope");
console.log($scope.ageGroups);
ngDialog.close();
});
}, function(errResponse) {
console.log("Failed on attempt to add age group:");
console.log(errResponse);
});
};
The coreDataService is defined as follows:
'use strict';
angular.module('ma-app')
.service('coreDataService', ['$http', 'baseURL', 'googleGeolocateBaseURL', 'googleGeocodeKey', 'googleMapsBaseURL', function($http, baseURL, googleGeolocateBaseURL, googleGeocodeKey, googleMapsBaseURL) {
var ageGroups = {};
var ageGroupsLoaded = false;
this.getAgeGroups = function() {
return ageGroups;
};
this.setAgeGroups = function(newAgeGroups) {
ageGroups = newAgeGroups;
ageGroupsLoaded = true;
};
this.addAgeGroup = function(formData) {
//post age group:
var postString = '{ "birth_year": "' + formData.birthyear + '", "soccer_year": "U' + formData.socceryear + '", "name": "' + formData.name + '" }';
console.log("Posting age group with string: " + postString);
return $http({
url: baseURL + 'age_groups/',
method: 'POST',
headers: {
'content-type': 'application/json'
},
data: postString
});
};
}]);
So, when an 'age group' is added, console logging indicates that the new 'age group' is in the collection now stored in the $scope.ageGroups array, but the HTML's ng-repeat does not reflect the new 'age group'. Only when I navigate to another tab in the interface, and then return to the tab containing the 'age groups' is the newly added 'age group' displayed.
Updated:
After you update your value you need to tell angular that its been updated. If you wrap your code in a $timeout. On the next digest cycle the view will get updated. See here for more information.
...
$timeout(function() {
$scope.ageGroups = coreDataService.getAgeGroups();
// anything you want can go here and will safely be run on the next digest.
})
...
The $timeout basically runs on the next $digest cycle thus updating your value in the view.
I would suggest wrapping your ng-repeat part in a directive, fire an event on add method and listen to it from the directive:
this.addAgeGroup = function(formData) {
// do stuff
$scope.$broadcast('itemadded');
}
Inside your directive link function:
$scope.$on('itemadded', function() {
$scope.ageGroups = coreDataService.getAgeGroups();
});
I would recommend using the ControllerAs syntax which will refresh the ng-repeat, if the array to which it is bound to gets updated.
Refer the answer given here - ng-repeat not updating on update of array
I am using the following versions:
Ionic, v1.0.0-beta.14
AngularJS v1.3.6
Route configuration:
myApp.config(function ($stateProvider) {
$stateProvider
.state('manager_add', {
url: '/managers/add',
templateUrl: 'app/components/mdl.role_members/views/add.html',
controller: 'ManagerAddController'
});
});
Controller configuration:
myApp.controller('ManagerAddController', function ($scope, $state, $ionicLoading, $filter, ContactService, ManagersService, RoleRequest, RoleRequestsSentService, ToastrService) {
$scope.role_request = RoleRequest.new();
$scope.showContactSearch = false;
$scope.managers = ManagersService.collection();
$scope.$watchCollection("managers", function( newManagers, oldManagers ) {
if(newManagers === oldManagers){ return; }
$scope.managers = newManagers;
$scope.contactsToBeInvited = getNotInvitedContacts();
});
$scope.contacts = ContactService.collection();
$scope.$watchCollection("contacts", function( newContacts, oldContacts ) {
if(newContacts === oldContacts){ return; }
$scope.contacts = newContacts;
$scope.contactsToBeInvited = getNotInvitedContacts();
});
$scope.contactsToBeInvited = getNotInvitedContacts();
function getNotInvitedContacts() {
var notinvited = [];
angular.forEach($scope.contacts, function(contact) {
if(angular.isObject($scope.managers)) {
var results = $filter('filter')($scope.managers, {member_id: Number(contact.contact_id)}, true);
if (results.length == 0) {
this.push(contact);
}
} else {
this.push(contact);
}
}, notinvited);
return notinvited;
}
$scope.search_contact = "";
$scope.search = function(contact) {
if($scope.search_contact === "" || $scope.search_contact.length === 0) {
return true;
}
$scope.showContactSearch = true;
var found = false;
if(contact.display_name) {
found = (contact.display_name.toLowerCase().indexOf($scope.search_contact.toLowerCase()) > -1);
if(found) { return found; }
}
if(contact.contact.getFullName()) {
found = (contact.contact.getFullName().toLowerCase().indexOf($scope.search_contact.toLowerCase()) > -1);
if(found) { return found; }
}
if(contact.contact.email) {
found = (contact.contact.email.toLowerCase().indexOf($scope.search_contact.toLowerCase()) > -1);
if(found) { return found; }
}
return found;
}
$scope.selectContact = function(contact) {
$scope.search_contact = contact.contact.getFullName();
// TODO: Make dynamic role
$scope.role_request.role_id = 4;
$scope.role_request.email = contact.contact.email;
};
$scope.addRoleMember = function(valid) {
if($scope.role_request.email === "") { return; }
if(!valid) { return; }
$ionicLoading.show({
template: 'Please wait...'
});
RoleRequestsSentService.add($scope.role_request).then(function(roleJsonResponse){
ToastrService.toastrSuccess('Request send', 'We have send an invite to '+ $scope.search_contact +'.');
$ionicLoading.hide();
$state.go('managers');
});
}
});
View configuration:
<ion-view view-title="ManagerAdd" >
<ion-content class="has-header scroll="true">
<div class="content">
<div class="list">
<div class="item item-border">
<p>Some text</p>
</div>
</div>
<form name="managerForm">
<div class="list">
<div class="item item-divider">
Some text
</div>
<div class="item item-border">
<form name="fillForm">
<div class="form-group">
<label class="item item-input item-stacked-label item-textarea">
<span class="input-label border-none">Personal message: <span class="text-red required">*</span></span>
<textarea name="message" ng-model="role_member.message" required></textarea>
</label>
<p ng-show="managerForm.message.$dirty && managerForm.message.$error.required"
class="error-message">Message required!</p>
</div>
<div class="form-group">
<label class="item item-input">
<span class="input-label">Search on name <span class="text-red required">*</span></span>
<input type="text" name="search_contact" ng-model="$parent.search_contact">
</label>
<div class="searchResultBox" ng-show="showContactSearch">
<ion-scroll direction="y" class="scrollArea">
<div class="list">
<a class="item item-border item-avatar pointer" ng-repeat="contact in $parent.filteredContacts = (contactsToBeInvited | filter:search:false)" ng-click="$parent.selectContact(contact)">
<img src="{{ contact.getImage('thumbnail') }}">
<h2>{{contact.getIconName()}}</h2>
<p>City: {{contact.contact.city}}</p>
</a>
</div>
</ion-scroll>
<div class="notFound pointer" ng-hide="filteredContacts.length">
<h3>Nobody found</h3>
<p>You can only search through existing contacts</p>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="form-actions">
<button type="submit" class="button button-block regie-button" ng-click="addRoleMember(registerForm.$valid)">
Sent request
</button>
</div>
</form>
<p class="text-red" style="text-align:center; font-size:14px; font-weight: 400;">* required</p>
</div>
</ion-content>
</ion-view>
As you can see in the view I need to use $parent to the following fields to get it to work:
ng-model="$parent.search_contact"
ng-repeat="contact in $parent.filteredContacts = (contactsToBeInvited | filter:search:false)"
ng-click="$parent.selectContact(contact)"
I really don't understand why this is necessary because the complete view is using the same controller. Does anyone have an idea?
This is a very typical angular scoping issue. Ion-view creates a new child scope and uses prototypical inheritance: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Three are several ways to solve:
always use a dot: https://egghead.io/lessons/angularjs-the-dot
use the 'controller as' syntax: http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
The problem is the inheritance. Between your controller's scope and those fields, there are several new scopes (ion-content, ion-scroll and probably others).
So for example the ng-model="search_content". When you write in there, it is going to create a search_content variable inside the ion-content scope (or an intermediary scope if there is any that I didn't see). Since search_content is being created inside ion-content, your controller won't see it.
If you do $parent.search_content it will create it in the parent content (AKA the controller's scope).
You shouldn't do that, what $parent is for you today, tomorrow it can point to anything else (because you could add a new scope in between without knowing it so $parent will then point to the ion-content.
So instead of doing that, you need to use objects and no primitives, for example:
ng-model="form.search_contact"
Thank to that, it will look for the form object through the prototype chain until it finds it on the controller's scope and use it (just what you need).
Read this which is hundred times better than my explanation.