I am looking to run the following controller but im having trouble with scope.
I have a service that calls two functions that retrieve meta data to populate scope variables.
The issue is that using the service to call back the data interferes with other actions happening in the code. I have a directive on a tag that shows/hides an error on the span element once the rule is validated. This is now not functioning correctly. I run the code without asynchronous functions then everything works correctly.
My Plunker code is here
and the plunker of the desired behaviour is here
Plunker working example without dynamic data loading
<form class="form-horizontal">
<div class="control-group" ng-repeat="field in viewModel.Fields">
<label class="control-label">{{field.label}}</label>
<div class="controls">
<input type="text" id="{{field.Name}}" ng-model="field.data" validator="viewModel.validator" ruleSetName="{{field.ruleSet}}"/>
<span validation-Message-For="{{field.Name}}"></span>
</div>
</div>
<button ng-click="save()">Submit</button>
</form>
How do I get all bindings to update so everything is sync and loaded correctly?
angular.module('dataApp', ['servicesModule', 'directivesModule'])
.controller('dataCtrl', ['$scope', 'ProcessService', 'ValidationRuleFactory', 'Validator',
function($scope, ValidationRuleFactory, Validator, ProcessService) {
$scope.viewModel = {};
var FormFields = {};
// we would get this from the meta api
ProcessService.getProcessMetaData().then(function(data) {
alert("here");
FormFields = {
Name: "Course",
Fields: [{
type: "text",
Name: "name",
label: "Name",
data: "",
required: true,
ruleSet: "personFirstNameRules"
}, {
type: "text",
Name: "description",
label: "Description",
data: "",
required: true,
ruleSet: "personEmailRules"
}]
};
$scope.viewModel.Fields = FormFields;
ProcessService.getProcessRuleData().then(function(data) {
var genericErrorMessages = {
required: 'Required',
minlength: 'value length must be at least %s characters',
maxlength: 'value length must be less than %s characters'
};
var rules = new ValidationRuleFactory(genericErrorMessages);
$scope.viewModel.validationRules = {
personFirstNameRules: [rules.isRequired(), rules.minLength(3)],
personEmailRules: [rules.isRequired(), rules.minLength(3), rules.maxLength(7)]
};
$scope.viewModel.validator = new Validator($scope.viewModel.validationRules);
});
});
var getRuleSetValuesMap = function() {
return {
personFirstNameRules: $scope.viewModel.Fields[0].data,
personEmailRules: $scope.viewModel.Fields[1].data
};
};
$scope.save = function() {
$scope.viewModel.validator.validateAllRules(getRuleSetValuesMap());
if ($scope.viewModel.validator.hasErrors()) {
$scope.viewModel.validator.triggerValidationChanged();
return;
} else {
alert('person saved in!');
}
};
}
]);
The validation message directive is here
(function(angular, $) {
angular.module('directivesModule')
.directive('validationMessageFor', [function() {
return {
restrict: 'A',
scope: {eID: '#val'},
link: function(scope, element, attributes) {
//var errorElementId = attributes.validationMessageFor;
attributes.$observe('validationMessageFor', function(value) {
errorElementId = value;
//alert("called");
if (!errorElementId) {
return;
}
var areCustomErrorsWatched = false;
var watchRuleChange = function(validationInfo, rule) {
scope.$watch(function() {
return validationInfo.validator.ruleSetHasErrors(validationInfo.ruleSetName, rule.errorCode);
}, showErrorInfoIfNeeded);
};
var watchCustomErrors = function(validationInfo) {
if (!areCustomErrorsWatched && validationInfo && validationInfo.validator) {
areCustomErrorsWatched = true;
var validator = validationInfo.validator;
var rules = validator.validationRules[validationInfo.ruleSetName];
for (var i = 0; i < rules.length; i++) {
watchRuleChange(validationInfo, rules[i]);
}
}
};
// get element for which we are showing error information by id
var errorElement = $("#" + errorElementId);
var errorElementController = angular.element(errorElement).controller('ngModel');
var validatorsController = angular.element(errorElement).controller('validator');
var getValidationInfo = function() {
return validatorsController && validatorsController.validationInfoIsDefined() ? validatorsController.validationInfo : null;
};
var validationChanged = false;
var subscribeToValidationChanged = function() {
if (validatorsController.validationInfoIsDefined()) {
validatorsController.validationInfo.validator.watchValidationChanged(function() {
validationChanged = true;
showErrorInfoIfNeeded();
});
// setup a watch on rule errors if it's not already set
watchCustomErrors(validatorsController.validationInfo);
}
};
var getErrorMessage = function(value) {
var validationInfo = getValidationInfo();
if (!validationInfo) {
return '';
}
var errorMessage = "";
var errors = validationInfo.validator.errors[validationInfo.ruleSetName];
var rules = validationInfo.validator.validationRules[validationInfo.ruleSetName];
for (var errorCode in errors) {
if (errors[errorCode]) {
var errorCodeRule = _.findWhere(rules, {errorCode: errorCode});
if (errorCodeRule) {
errorMessage += errorCodeRule.validate(value).errorMessage;
break;
}
}
}
return errorMessage;
};
var showErrorInfoIfNeeded = function() {
var validationInfo = getValidationInfo();
if (!validationInfo) {
return;
}
var needsAttention = validatorsController.ruleSetHasErrors() && (errorElementController && errorElementController.$dirty || validationChanged);
if (needsAttention) {
// compose and show error message
var errorMessage = getErrorMessage(element.val());
// set and show error message
element.text(errorMessage);
element.show();
} else {
element.hide();
}
};
subscribeToValidationChanged();
if (errorElementController)
{
scope.$watch(function() {
return errorElementController.$dirty;
}, showErrorInfoIfNeeded);
}
scope.$watch(function() {
return validatorsController.validationInfoIsDefined();
}, subscribeToValidationChanged());
});
}
};
}]);
})(angular, $);
Related
I have a problem where I'm trying to ng-show a view using ng-view when the user is logged in. By default the value vm.isLoggedIn is false when the controller initializes and vm.isLoggedIn gets set to true when they are logged in.
But this doesn't update the UI to reflect the changes, the ng-show will still have class="ng-hide", which is put there by AngularJS when you decalred it with ng-show.
How do I get the UI to reflect the changes when vm.isLoggedIn is updated to true on a return of a promise?
Here is the HTML:
<div id="loggedInContainer" ng-show="vm.isLoggedIn">
<div ng-view></div>
</div>
This is my controller where everything is being set:
(function () {
"use strict";
var app = angular.module("productManagement");
var MainCtrl = function ($scope, $timeout, userAccount) {
var vm = this;
vm.isLoggedIn = false;
vm.message = "";
vm.userData = {
userName: "",
email: "",
password: "",
confirmPassword: ""
};
var applyChanges = function () {
$timeout(function () {
$scope.$apply();
});
}
vm.registerUser = function () {
vm.userData.confirmPassword = vm.userData.password;
vm.userData.userName = vm.userData.email;
userAccount.registration.registerUser(vm.userData, function (data) {
vm.confirmPassword = "";
vm.message = "Registration Successful";
vm.login();
}, function (errorResponse) {
vm.isLoggedIn = false;
vm.message = errorResponse.statusText + "\r\n";
if (errorResponse.data.exceptionMessage) {
vm.message += errorResponse.data.exceptionMessage;
}
// Validation Errors
if (errorResponse.data.modelState) {
for (var key in errorResponse.data.modelState) {
vm.message += errorResponse.data.modelState[key] + "\r\n";
}
}
});
}
vm.login = function () {
vm.userData.grant_type = "password";
vm.userData.userName = vm.userData.email;
userAccount.login.loginUser(vm.userData, function (data) {
vm.isLoggedIn = true; // This is where vm.isLoggedIn is set to true
vm.message = "";
vm.password = "";
vm.token = data.access_token;
//$("#loggedInContainer").removeClass("ng-hide"); // Hack used to manually remove the class
}, function (errorResponse) {
vm.password = "";
vm.isLoggedIn = false;
vm.message = errorResponse.statusText + "\r\n";
if (errorResponse.data.exceptionMessage) {
vm.message += errorResponse.data.exceptionMessage;
}
if (errorResponse.data.error) {
vm.message += errorResponse.data.error;
}
});
}
}
app.controller("MainCtrl", ["$scope", "$timeout", "userAccount", MainCtrl]);
})();
This is the service I'm using to Login the user, which uses $resource
(function () {
"use strict";
var app = angular.module("common.services");
var userAccount = function ($resource, $http, appSettings) {
return {
registration: $resource(appSettings.serverPath + "/api/Account/Register", null,
{
"registerUser": { method: "POST" }
}),
login: $resource(appSettings.serverPath + "/Token", null,
{
"loginUser": {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
transformRequest: function (data, headersGetter) {
var str = [];
for (var d in data) {
str.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
}
return str.join("&");
}
}
})
}
}
app.factory("userAccount", ["$resource", "$http", "appSettings", userAccount]);
})();
I've tried to manually use $scope.$apply(), $scope.$digest(), even $timeout(), but none of them are working properly. I either get a $digest is already running error or nothing happens at all.
The only work around I can use is to manually remove the class on the div using jQuery, but that's not the correct way to do it using AngualrJS.
I have a controller that works fine on initial load. It calls user [0] data and everything processes fine. When I change dropdown, I want to call the same function, but I cannot get the entire controller to reload. It starts from the function call and leaves undefined in places where it pulls correct information (linkToken, etc) on initial load. Is there a way I can get it to reload all data from the controller instead of just from the function?
After calling the testchange() from the view to pass in the new data, I get :
TypeError: Cannot read property 'locations' of undefined
at h.$scope.updateLocations (refillController.js:261)
at refillController.js:73
But, when I call the original getRefills() that is ran on the initial page load I get the same error. How can I define these so the load after the onchange(0)?
angular.module('FinalApp.Controllers').controller('refillController', function ($rootScope, $scope, $location, $modal, userService, dependentsService, accountService, sharedCollections, configurationService, refillsService) {
$scope.user = userService.GetUserInformation();
$scope.userInfoArr = [];
//$scope.tests.push({'Name':$scope.user.FirstName, 'LinkToken':$scope.user.LinkToken});
$scope.userInfoArr = $scope.user.Dependants.map(function(item){return {'Name':item.FirstName, 'LinkToken':item.LinkToken}});
$scope.userInfoArr.splice(0, 0, {'Name': $scope.user.FirstName, 'LinkToken': $scope.user.LinkToken});
console.log($scope.userInfoArr);
$scope.finUserInfoArr = $scope.userInfoArr[0].LinkToken;
$scope.billingInfo = null;
$rootScope.showNavbar = true;
$scope.selectedMethod = null;
$scope.location = null;
$scope.payment = null;
$scope.refills = [];
$scope.deliverTypes = [];
$scope.locations = [];
$scope.payments = [];
$scope.allSelected = false;
$scope.loadingBillingInfo = false;
$scope.isMailOrder = false;
//Detect Mobile Switch Refill List To Grid
if(window.innerWidth <= 800) {
$scope.view = "Grid";
} else {
$scope.view = "List";
}
$scope.changeViewToList = function () {
$scope.view = "List";
};
$scope.changeViewToGrid = function () {
$scope.view = "Grid";
};
$scope.testchange = function(selectedTest) {
$scope.getRefills(selectedTest);
console.log(selectedTest);
};
$scope.getRefills = function (linkToken) {
$scope.allSelected = false;
$scope.loading = true;
refillsService.GetRefills(
linkToken,
$scope.selectedMethod,
$scope.location,
$scope.payment
).then(function (data) {
$scope.refills = [];
_.each(data.Prescriptions, function (item) {
fillRefills(item);
});
fillDeliverTypes(data.DeliveryTypes);
if (!$scope.selectedMethod)
$scope.selectedMethod = data.DeliveryTypeId;
if (!$scope.location)
$scope.location = data.PickupLocationId;
if (!$scope.payment)
$scope.payment = data.PaymentTypeId;
$scope.loading = false;
$scope.updateLocations();
})["catch"](function (error) {
console.log(error);
$scope.loading = false;
alertify.alert(configurationService.ErrorMessage("getting your refills", error.Message));
});
};
var fillRefills = function (item) {
//TODO-CallDoc temp fix
if (item.RefillClass == "CALL_DOCTOR") {
item.NextRefillDate = '1900-01-01T00:00:00'
}
var parsedDate = checkDate(moment(item.NextRefillDate).format('L'));
var lastrefill = checkDate(moment(item.LastDispenseDate).format('L'));
var expireDate = checkDate(moment(item.ExpirationDate).format('L'));
var status = (item.RefillStatus.indexOf("After") == -1) ? item.RefillStatus : "Refill " + item.RefillStatus;
$scope.refills.push({
selected: false,
rx: item.ScriptNo,
name: item.DrugName,
dose: item.UnitsPerDose,
dir: item.Instructions,
nextfill: parsedDate,
lastfill: lastrefill,
refillsLeft: item.NumRefillsLeft,
status: status,
msg: item.RefillMessage,
canSelect: item.IsRefillable,
refillClass: item.RefillClass,
lastDispenseQty: item.LastDispenseQty,
DaysSupply: item.DaysSupply,
expireDate: expireDate,
copayAmt: item.CopayAmt,
drFirstName: item.DoctorFirstName,
drLastName: item.DoctorLastName,
writtenQty: item.WrittenQty
});
};
var checkDate = function (date) {
if (date == "01/01/1900") return "N/A";
if (date == "Invalid Date") return "";
return date;
};
var fillDeliverTypes = function (deliverTypes) {
$scope.deliverTypes = [];
_.each(deliverTypes, function (item) {
$scope.deliverTypes.push({
id: item.DeliveryTypeId,
name: item.DeliveryTypeName,
locations: item.PickupLocations,
payments: item.PaymentTypes
});
});
};
var getBillingInfo = function () {
$scope.loadingBillingInfo = true;
accountService.GetCreditCardInfo().then(function (data) {
$scope.billingInfo = data;
$scope.loadingBillingInfo = false;
})["catch"](function (error) {
$scope.loadingBillingInfo = false;
alertify.alert(configurationService.ErrorMessage("getting account information", error.Message));
});
};
var getAccountInfo = function () {
accountService.GetAccountInfo().then(function (data) {
if (data.StatusCode == "SUCCESS") {
$scope.user = data;
userService.SaveUserInformation(data);
if ($scope.user.IsLinked) {
$rootScope.enableMyRefills = true;
$rootScope.enableMyReports = true;
window.location.hash = "#/refills";
} else {
$rootScope.enableMyRefills = false;
$rootScope.enableMyReports = true;
}
} else {
alertify.alert(configurationService.ErrorMessage("getting account information", data.StatusMessage));
}
})["catch"](function (error) {
alertify.alert(configurationService.ErrorMessage("getting account information", error.Message));
});
};
var openModal = function (viewUrl, controllerUrl, size, payload) {
var modalInstance = $modal.open({
templateUrl: viewUrl,
controller: controllerUrl,
size: size,
resolve: {
data: function () {
return payload;
}
}
});
return modalInstance;
};
$scope.toggleRxSelection = function(rx) {
if (rx.canSelect) {
rx.selected = !rx.selected;
$scope.evaluateAllSelected();
}
};
$scope.selectAll = function (data) {
// $scope.allSelected = allSelected;
_.each($scope.refills, function (x) {
if (x.canSelect) x.selected = data;
});
};
$scope.evaluateAllSelected = function () {
var count = _.countBy(_.where($scope.refills, {canSelect:true}), function(refill) {
return refill.selected ? 'selected' : 'not';
});
$scope.allSelected = (count.not === undefined);
};
$scope.openEditCreditCardInfo = function () {
var payload = ($scope.billingInfo != null && $scope.billingInfo != undefined)
&& $scope.billingInfo.CardNumber != "" ? $scope.billingInfo : {};
if (payload != {}) {
payload.ExpMonth = {id: parseInt(payload.ExpMonth)};
payload.ExpYear = {id: parseInt(payload.ExpYear)};
}
openModal('app/views/editAccount/billingInformation.html', "billingInformationController", "xlg", payload).result.then(function () {
getAccountInfo();
getBillingInfo();
}, function () {
getBillingInfo();
});
};
$scope.openConfirmOrder = function () {
var refillsSelected = _.where($scope.refills, {selected: true});
var location = _.findWhere($scope.locations, {PickupLocationId: $scope.location});
var payment = _.findWhere($scope.payments, {PaymentTypeId: $scope.payment});
var deliver = _.findWhere($scope.deliverTypes, {id: $scope.selectedMethod});
if (refillsSelected.length == 0) {
alertify.error("You need to select at least one refill");
return;
}
if (deliver.id == 10001 && !$scope.user.IsCreditCardOnFile) {
alertify.error("Need credit card on file for mail order");
return;
}
sharedCollections.setRefills(refillsSelected);
sharedCollections.setLocation(location);
sharedCollections.setPayment(payment);
sharedCollections.setDeliver(deliver);
openModal('app/views/refills/confirmOrder.html', "confirmOrderController", "xlg").result.then(function () {
$scope.billingInfo = accountService.GetCreditCardInfo();
$scope.getRefills();
}, function () {
//$scope.billingInfo = accountService.GetCreditCardInfo();
//getRefills();
});
};
$scope.showRefill = function (rx) {
var data = {rx: rx, refills: $scope.refills};
openModal('app/views/refills/showRefills.html', "refillsCarrousel", "xlg", data).result.then(function () {
$scope.evaluateAllSelected();
}, function () {
$scope.evaluateAllSelected();
});
};
$scope.updateLocations = function () {
$scope.locations = _.findWhere($scope.deliverTypes, {id: $scope.selectedMethod}).locations;
$scope.payments = _.findWhere($scope.deliverTypes, {id: $scope.selectedMethod}).payments;
setLocationAndPayment();
};
var setLocationAndPayment = function () {
if ($scope.locations.length == 1) {
$scope.location = $scope.locations[0].PickupLocationId;
}
if ($scope.payments.length == 1) {
$scope.payment = $scope.payments[0].PaymentTypeId;
}
//check for mail order
($scope.selectedMethod == 10001 && !$scope.payment) ? $scope.isMailOrder = true : $scope.isMailOrder = false;
};
$scope.getRefills($scope.finUserInfoArr);
getBillingInfo();
});
Check if your refillsService returns correct data, it could be that $scope.refills remains empty array.
I have a controller which has a function to get some alerts from an API and update a count on the front-end of my site which is bound to the alert.
Unfortunately the ng-bind attribute I'm using doesn't seem to be updating the count live, even though a simple console.log() is telling me that the actual alert count is being updated in the controller.
Front-end
<div class="modeSelector modeSelector_oneUp" data-ng-controller="MyLivestockController as vm">
<a class="modeSelector-mode" data-ui-sref="my-livestock">
<div class="modeSelector-type">Alerts</div>
<img class="modeSelector-icon" src="/inc/img/_icons/envelope-black.svg" onerror="this.src=envelope-black.png" />
<span data-ng-bind="vm.alertCount"></span>
</a>
</div>
Controller
(function() {
'use strict';
function MyLivestockController(userService) {
var vm = this;
vm.myLivestockNotification = {
isLoading: true,
hasError: false
};
vm.alertsNotification = {
isLoading: true,
hasError: false,
hasData: false
};
vm.deleteAlert = function(id) {
vm.currentAlert = void 0;
vm.alertsNotification.isLoading = true;
userService.deleteAlert(vm.user.id, id).then(function() {
// Remove the alert from our Array
vm.alerts = vm.alerts.filter(function(alert) {
return alert.id !== id;
});
// Refresh the alert count for the user
vm.getAlerts(vm.user.id);
vm.alertsNotification.isLoading = false;
vm.alertsNotification.hasError = false;
}, function() {
vm.alertsNotification.hasError = true;
});
};
vm.getAlerts = function(id) {
userService.getAlerts(id).then(function(alertData) {
vm.alertCount = alertData.length;
if (vm.alertCount > 0) {
vm.alertsNotification.hasData = true;
} else {
vm.alertsNotification.hasData = false;
}
vm.alerts = alertData;
vm.alertsNotification.isLoading = false;
vm.alertsNotification.hasError = false;
}, function() {
vm.alertsNotification.hasError = true;
});
};
// Init
(function() {
userService.getCurrentUser().then(function(data) {
vm.myLivestockNotification.hasError = false;
vm.myLivestockNotification.isLoading = false;
vm.user = data;
// Get alert count for the user
vm.getAlerts(vm.user.id);
}, function() {
vm.myLivestockNotification.hasError = true;
});
})();
}
angular
.module('abp')
.controller('MyLivestockController', MyLivestockController);
})();
Service
(function() {
'use strict';
function userService($q, $sessionStorage, $localStorage, $filter, user) {
var service = this;
service.getAlerts = function(id) {
var deferred = $q.defer();
user.alerts({ userID: id }, function(response) {
if (response.hasOwnProperty('data')) {
// Convert dates to valid Date
angular.forEach(response.data, function(alert) {
/* jshint camelcase: false */
if (alert.created_at) {
alert.created_at = $filter('abpDate')(alert.created_at);
/* jshint camelcase: true */
}
});
deferred.resolve(response.data);
}
else {
deferred.reject('DATA ERROR');
}
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
};
angular
.module('abp')
.service('userService', userService);
})();
As you can see, I've got my getAlerts() function being called every time an alert is deleted, using the deleteAlert() function, but the <span data-ng-bind="vm.alertCount"></span> on the front-end only updates after refreshing the page, where I'd like it to update live.
Your bind is not updating because you change the value of alertCount outside of digest cycle of your angular app. When you refresh your app, the digest runs and thus your value gets updated. Wrap the update of the variable in $scope.apply() like so:
$scope.$apply(function(){
vm.alertCount = alertData.length;
});
This will force digest and update the value live.
If you have more values that are updated outside of digest (any callback, promise etc) you can force digest cycle by calling:
$scope.$apply();
Hope it helps.
EDIT -----
Given your update with full code, I see that you are not injecting scope anywhere in your controller, the controllers I write usually start like that:
(function () {
var app = angular.module('mainModule');
app.controller('myController', ['$scope', '$myService', function ($scope, $myService) {
//logic
}]);
}());
EDIT -----
Here is a quick go I had on your code:
(function() {
'use strict';
var app = angular.module('abp');
app.controller('MyLivestockController', ['$scope', 'userService', function($scope, userService) {
var vm = {};
$scope.vm = vm;
vm.myLivestockNotification = {
isLoading: true,
hasError: false
};
vm.alertsNotification = {
isLoading: true,
hasError: false,
hasData: false
};
vm.deleteAlert = function(id) {
vm.currentAlert = void 0;
vm.alertsNotification.isLoading = true;
userService.deleteAlert(vm.user.id, id).then(function() {
// Remove the alert from our Array
vm.alerts = vm.alerts.filter(function(alert) {
return alert.id !== id;
});
// Refresh the alert count for the user
vm.getAlerts(vm.user.id);
vm.alertsNotification.isLoading = false;
vm.alertsNotification.hasError = false;
}, function() {
vm.alertsNotification.hasError = true;
});
};
vm.getAlerts = function(id) {
userService.getAlerts(id).then(function(alertData) {
vm.alertCount = alertData.length;
if (vm.alertCount > 0) {
vm.alertsNotification.hasData = true;
} else {
vm.alertsNotification.hasData = false;
}
vm.alerts = alertData;
vm.alertsNotification.isLoading = false;
vm.alertsNotification.hasError = false;
//important, this is promise so we have to apply the scope to update view
$scope.$apply();
}, function() {
vm.alertsNotification.hasError = true;
});
};
// Init
(function() {
userService.getCurrentUser().then(function(data) {
vm.myLivestockNotification.hasError = false;
vm.myLivestockNotification.isLoading = false;
vm.user = data;
// Get alert count for the user
vm.getAlerts(vm.user.id);
}, function() {
vm.myLivestockNotification.hasError = true;
});
})();
}]);
})();
The general idea is:
you create an app (angular.module)
you create a controller in this app, with $scope injected
any values you want to be updated on your view, you add to $scope
if you have any $scope updates in a callback, event or promise, you wrap them in (or follow with) $scope.$apply call
I think this should work for you :)
I have attempted to reproduce your code below with a mock userService, and some slight modifications to the html view so we can more clearly see the alerts and delete them. I have not modified your Controller.
This appears to work, yes?
Which leads me to believe there may be some issue with the implementation of your userService. If you are able to post the relevant code, I can update this answer with a clarified solution.
UPDATE: As you've updated your question with the userService code, I've updated the below to more closely match. I still have a mock service standing in place of the user dependency of the userService. Additionally I made a couple of small edits to the Controller class so that while promises are still resolving we can see 'Updating...' in place of the alerts count.
This all still appears to work, unless I'm misunderstanding - will think on it more and update this 'answer' when I can think of where else to investigate for the source of the issue, see if we can at least reproduce it!
(function() {
'use strict';
function MyLivestockController(userService) {
var vm = this;
vm.myLivestockNotification = {
isLoading: true,
hasError: false
};
vm.alertsNotification = {
isLoading: true,
hasError: false,
hasData: false
};
vm.deleteAlert = function(id) {
vm.currentAlert = void 0;
vm.alertsNotification.isLoading = true;
return userService.deleteAlert(vm.user.id, id).then(function() {
// Remove the alert from our Array
vm.alerts = vm.alerts.filter(function(alert) {
return alert.id !== id;
});
// Refresh the alert count for the user
vm.getAlerts(vm.user.id).then(function() {
vm.alertsNotification.isLoading = false; //put here, loading isn't really finished until after .getAlerts() is done
vm.alertsNotification.hasError = false;
});
}, function() {
vm.alertsNotification.hasError = true;
});
};
vm.getAlerts = function(id) {
vm.alertsNotification.isLoading = true;
return userService.getAlerts(id).then(function(alertData) { //return the promise so we can chain .then in .deleteAlert()
vm.alertCount = alertData.length;
if (vm.alertCount > 0) {
vm.alertsNotification.hasData = true;
} else {
vm.alertsNotification.hasData = false;
}
vm.alerts = alertData;
vm.alertsNotification.isLoading = false;
vm.alertsNotification.hasError = false;
}, function() {
vm.alertsNotification.hasError = true;
});
};
// Init
(function() {
userService.getCurrentUser().then(function(data) {
vm.myLivestockNotification.hasError = false;
vm.myLivestockNotification.isLoading = false;
vm.user = data;
// Get alert count for the user
vm.getAlerts(vm.user.id);
}, function() {
vm.myLivestockNotification.hasError = true;
});
})();
}
function userMock($q, $timeout, $log) {
var _alerts = {
data: [{
id: 1,
message: "He doesn't sleep, he waits..."
}, {
id: 2,
message: "He doesn't mow his lawn, he stands outside and dares it to grow."
}, {
id: 3,
message: "Some magicians can walk on water. He can swim through land."
}]
},
_currentUser = {
id: 'Q2h1Y2sgTm9ycmlz'
};
return {
getCurrentUser: function getCurrentUser() {
$log.log("getCurrentUser");
//return $q.when(_currentUser);
return $timeout(function() { //use $timeout to simulate some REST API latency...
return _currentUser;
}, 500);
},
getAlerts: function getAlerts(id) {
$log.log("getAlerts: " + id); //not doing anything with the id in this mock...
$log.log(_alerts.data);
//return $q.when(_alerts);
return $timeout(function() {
return _alerts;
}, 500);
},
deleteAlert: function deleteAlert(userId, id) {
$log.log("deleteAlert: " + userId + " :: " + id);
//return $q.when(_alerts);
return $timeout(function() {
for (var i = 0; i < _alerts.data.length; i++) {
if (_alerts.data[i].id === id) {
_alerts.data.splice(i, 1);
$log.log("alert found and deleted");
break;
}
}
$log.log(_alerts.data);
return _alerts;
}, 500);
}
};
}
function userService($q, $timeout, $log, userMock) {
var service = this;
service.getCurrentUser = userMock.getCurrentUser;
service.getAlerts = function(id) {
var deferred = $q.defer();
userMock.getAlerts(id).then(function(response) {
if (response.hasOwnProperty('data')) {
// Convert 'he' to 'Chuck Norris'
angular.forEach(response.data, function(alert) {
if (alert.message) {
alert.message = alert.message.replace(/he/gi, "Chuck Norris");
}
});
deferred.resolve(response.data);
} else {
deferred.reject('DATA ERROR');
}
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
};
service.deleteAlert = function(userId, id) {
var deferred = $q.defer();
userMock.deleteAlert(userId, id).then(function(response) {
deferred.resolve(response.data);
}, function(e) {
deferred.reject('DATA ERROR');
});
return deferred.promise;
};
return service;
};
angular
.module('abp', [])
.service('userMock', userMock)
.service('userService', userService)
.controller('MyLivestockController', MyLivestockController);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="abp">
<div data-ng-controller="MyLivestockController as vm">
<div>Alerts</div>
<span data-ng-bind="vm.alertsNotification.isLoading ? 'Updating...' : vm.alertCount"></span>
<div data-ng-repeat="alert in vm.alerts">
{{alert.id}}: {{alert.message}}
<button ng-click="vm.deleteAlert(alert.id)">Delete</button>
</div>
</div>
</div>
I'm starting to learn and azure phonejs.
Todo list get through a standard example:
$(function() {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
// Read current data and rebuild UI.
// If you plan to generate complex UIs like this, consider using a JavaScript templating library.
function refreshTodoItems() {
var query = todoItemTable.where({ complete: false });
query.read().then(function(todoItems) {
var listItems = $.map(todoItems, function(item) {
return $('<li>')
.attr('data-todoitem-id', item.id)
.append($('<button class="item-delete">Delete</button>'))
.append($('<input type="checkbox" class="item-complete">').prop('checked', item.complete))
.append($('<div>').append($('<input class="item-text">').val(item.text)));
});
$('#todo-items').empty().append(listItems).toggle(listItems.length > 0);
$('#summary').html('<strong>' + todoItems.length + '</strong> item(s)');
}, handleError);
}
function handleError(error) {
var text = error + (error.request ? ' - ' + error.request.status : '');
$('#errorlog').append($('<li>').text(text));
}
function getTodoItemId(formElement) {
return $(formElement).closest('li').attr('data-todoitem-id');
}
// Handle insert
$('#add-item').submit(function(evt) {
var textbox = $('#new-item-text'),
itemText = textbox.val();
if (itemText !== '') {
todoItemTable.insert({ text: itemText, complete: false }).then(refreshTodoItems, handleError);
}
textbox.val('').focus();
evt.preventDefault();
});
// Handle update
$(document.body).on('change', '.item-text', function() {
var newText = $(this).val();
todoItemTable.update({ id: getTodoItemId(this), text: newText }).then(null, handleError);
});
$(document.body).on('change', '.item-complete', function() {
var isComplete = $(this).prop('checked');
todoItemTable.update({ id: getTodoItemId(this), complete: isComplete }).then(refreshTodoItems, handleError);
});
// Handle delete
$(document.body).on('click', '.item-delete', function () {
todoItemTable.del({ id: getTodoItemId(this) }).then(refreshTodoItems, handleError);
});
// On initial load, start by fetching the current data
refreshTodoItems();
});
and it works!
Changed for the use of phonejs and the program stops working, even mistakes does not issue!
This my View:
<div data-options="dxView : { name: 'home', title: 'Home' } " >
<div class="home-view" data-options="dxContent : { targetPlaceholder: 'content' } " >
<button data-bind="click: incrementClickCounter">Click me</button>
<span data-bind="text: listData"></span>
<div data-bind="dxList:{
dataSource: listData,
itemTemplate:'toDoItemTemplate'}">
<div data-options="dxTemplate:{ name:'toDoItemTemplate' }">
<div style="float:left; width:100%;">
<h1 data-bind="text: name"></h1>
</div>
</div>
</div>
</div>
This my ViewModel:
Application1.home = function (params) {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
var toDoArray = ko.observableArray([
{ name: "111", type: "111" },
{ name: "222", type: "222" }]);
var query = todoItemTable.where({ complete: false });
query.read().then(function (todoItems) {
for (var i = 0; i < todoItems.length; i++) {
toDoArray.push({ name: todoItems[i].text, type: "NEW!" });
}
});
var viewModel = {
listData: toDoArray,
incrementClickCounter: function () {
todoItemTable = client.getTable('todoitem');
toDoArray.push({ name: "Zippy", type: "Unknown" });
}
};
return viewModel;
};
I can easily add items to the list of programs, but from the server list does not come:-(
I am driven to exhaustion and can not solve the problem for 3 days, which is critical for me!
Specify where my mistake! Thank U!
I suggest you use a DevExpress.data.DataSource and a DevExpress.data.CustomStore instead of ko.observableArray.
Application1.home = function (params) {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
var toDoArray = [];
var store = new DevExpress.data.CustomStore({
load: function(loadOptions) {
var d = $.Deferred();
if(toDoArray.length) {
d.resolve(toDoArray);
} else {
todoItemTable
.where({ complete: false })
.read()
.then(function(todoItems) {
for (var i = 0; i < todoItems.length; i++) {
toDoArray.push({ name: todoItems[i].text, type: "NEW!" });
}
d.resolve(toDoArray);
});
}
return d.promise();
},
insert: function(values) {
return toDoArray.push(values) - 1;
},
remove: function(key) {
if (!(key in toDoArray))
throw Error("Unknown key");
toDoArray.splice(key, 1);
},
update: function(key, values) {
if (!(key in toDoArray))
throw Error("Unknown key");
toDoArray[key] = $.extend(true, toDoArray[key], values);
}
});
var source = new DevExpress.data.DataSource(store);
// older version
store.modified.add(function() { source.load(); });
// starting from 14.2:
// store.on("modified", function() { source.load(); });
var viewModel = {
listData: source,
incrementClickCounter: function () {
store.insert({ name: "Zippy", type: "Unknown" });
}
};
return viewModel;
}
You can read more about it here and here.
I have a widget like directive called waComments, it loads components via a RESTful service and displays them. In my view I'm using ng-repeat to loop over them and to render them with a button that if pressed Shows a new reply to form. This his handled by the waCommentsReply directive. One waComments widget has many child directives of type waCommentsReply. When the form is filled and submitted I want to add the new comment on top of my comments list. So both directives have to share the comments data.
I've tried to implement this here Sharing data between directives but without much success, the comment data is not updated when I add a new comment. I see that the RESTful API calls work and the data is returned, so this is not an issue.
Why is my implementation of Sharing data between directives not working in my case?
waCommentsReply directive:
waFrontend.directive('waCommentsReply', ['$rootScope', 'Comment', 'WaFormValidation', 'WaCommentStore', function($rootScope, Comment, WaFormValidation, WaCommentStore) {
return {
restrict: 'E',
templateUrl: '/stubs/comment-form.html',
transclude: true,
scope: {
replyTo: '#replyTo',
replyFormList: '=replyFormList',
loggedIn: '#loggedIn',
model: '#model',
id: '#id',
cancelButton: '#cancelButton'
},
controller: function($scope) {
$scope.comments = WaCommentStore;
if ($scope.cancelButton == undefined) {
$scope.cancelButton = true;
} else {
$scope.cancelButton = false;
}
$scope.comment = $scope.commentForm = {
Comment: {
author_name: '',
body: '',
model: $scope.model,
foreign_key: $scope.id,
parent_id: $scope.replyTo
}
};
$scope.$watch('replyFormList', function (newValue, oldValue) {
if (newValue) {
$scope.replyFormList = newValue;
}
});
if ($scope.loggedIn == undefined) {
$scope.loggedIn = false;
}
/**
* Handles the submission and response of a reply
*
* #return void
*/
$scope.reply = function() {
Comment.add($scope.comment).then(function(result) {
if (result.status == 'fail' || result.validation != undefined) {
$scope.validationErrors = result.validation;
WaFormValidation.validate(result.validation, $scope.commentForm);
} else if (result.status == 'success') {
//$scope.$parent.comments.unshift(result.data.comment);
//$scope.comments.unshift(result.data.comment);
$scope.comments.comments.unshift(result.data.comment);
//WaCommentStore.append($scope.model, $scope.id, result.data.comment);
$scope.comments, $scope.id, result.data.comment
$scope.comment = {};
$scope.replyFormList[$scope.replyTo] = false;
}
});
};
$scope.close = function() {
$scope.comment = {};
if ($scope.replyFormList[$scope.replyTo] != undefined) {
$scope.replyFormList[$scope.replyTo] = false;
}
}
}
};
}]);
WaCommentStore directive:
waFrontend.factory('WaCommentStore', function() {
return {
comments: []
};
});
waComments directive:
waFrontend.directive('waComments', ['$rootScope', 'Comment', 'WaCommentStore', function($rootScope, Comment, WaCommentStore) {
return {
restrict: 'E',
templateUrl: '/stubs/comments.html',
scope: {
model: '#commentModel',
id: '#commentFk'
},
controller: function($scope) {
$scope.comments = WaCommentStore;
$scope.loaded = false;
$scope.loadedMore = true;
$scope.currentPage = 1;
$scope.loggedIn = false;
$scope.paging = {};
$scope.replyFormList = {};
Comment.comments($scope.model, $scope.id).then(function(result) {
$scope.comments.comments.push.apply($scope.comments.comments, result.data.comments);
$scope.loggedIn = result.data.loggedIn;
$scope.paging = result.paging.Comment;
$scope.loaded = true;
});
$scope.loadMore = function() {
$scope.loadedMore = false;
if ($scope.paging.nextPage == false) {
//return false;
}
var options = {
page: $scope.paging.page + 1
};
Comment.comments($scope.model, $scope.id, options).then(function(result) {
$scope.comments.comments.push.apply($scope.comments.comments, result.data.comments);
$scope.paging = result.paging.Comment;
$scope.loadedMore = true;
});
};
$scope.submitComment = function() {
//alert($scope.author_name + $scope.body);
};
$scope.reply = function(replyId) {
$scope.replyFormList[replyId] = true;
}
}
};
}]);
since in both directive you defined scope: {} basically it means you defined those directives to use isolated scope.
with isolated scope, a scope/directive can't see what is in the parent scope.
however parent scope, can be affected by the child scope changes with 2 way binding definition.
https://docs.angularjs.org/guide/scope
try changing the shared data like this
waFrontend.factory('WaCommentStore', function() {
var comments = [];
var getComments = function() { return comments; }
var setComments = function(data) { comments = data; }
return {
getComments : getComments ,
setComments : setComments
};
});
I wanted to put it as a comments, but it would have been difficult to understand for you.
Please let me know if this works, else I will delete this answer.