(please try not to get hung up on any typo's or semantics that may have occurred while transferring it to this post. I know that the code successfully executes so my issue isn't typo's. Also while I am interested in other ways of performing this task - using a single controller for example - I am more interested in why THIS scenario is not working. It may be something that comes up again and it would be nice to already understand the issue)
I am trying to do something that i thought would be fairly simple and after following several tutorials and Stack Overflow posts (as well as other places) I cannot see what I am missing. I am hoping that a pair of fresh eyes can help!
The HTML contains a form where new products can be added (for brevity I have reduced the number of elements) and a list of P tags where products are displayed ... The goal is to add the new products to the list as soon as they are created, without, obviously, refreshing the page.
<div style="width: 300px; float: left; display: inline-block;" data-ng-controller="ProductFormCtrl" data-ng-model="svc">
<p>
Name:
<input type="url" id="txProductName" data-ng-model="svc.form.productName"/>
</p>
<button id="btnSubmit" data-ng-click="svc.addNewProduct()">submit</button>
{{ svc.Title }}
</div>
<div id="dvProductList" style="position:relative;float:left;" data-ng-controller="ProductListCtrl">
<h2>{{ svc.Title }} </h2>
<P data-ng-repeat="product in svc.products">{{ product.ProductName }}</P>
</div>
The form and the list are managed by separate controllers ... each of which references a ProductService with their $scope variable ...
var app = angular.module('productApp', []);
app.controller('ProductFormCtrl', ['$scope', '$http', 'ProductService', function ($scope, $http, $productSvc) {
$scope.svc = $productSvc;
$scope.svc.Title = "Tea";
}]);
app.controller('ProductListCtrl', ['$scope', '$http', 'ProductService', function ($scope, $http, $productSvc) {
$scope.svc = $productSvc;
}]);
In order to referesh the list, the thought was to bind the P element in the ProdcutListCtrl to the products property of the ProductService, and when a new product was added simply update the contents of the products property and voila! ... or not ...
The service defines the property "products" whose value is initialized by a call to web service. It's an array of lightweight product objects.
app.service('ProductService', ['$http', function ($http) {
var self = this;
self.Title = "Coffee";
//
// initialize the property to be referenced by both controllers
(function () {
$http.get("/API/ProductAPI/GetUserProducts/36d43a01-22e5-494d-9e46-2d4ea5df7001")
.success(function (data) {
self.products = data;
});
})();
//
// object to hold the form values
self.form = { productName: '' };
//
// calls web service to add a new product
self.addNewProduct = function () {
var formData = new FormData();
formData.append('ProductName', self.form.productName);
$.ajax({
type: "POST",
url: "/API/ProductAPI/AddUserProduct/36d43a01-22e5-494d-9e46-2d4ea5df7001",
data: formData,
processData: false,
contentType: false
})
.done(function (data) {
if (data != null) {
//
// These changes are not expressed in the User Interface
self.products.push(data); // Update the products property with the new product
self.Title = "Completed"; // Update the title property
}
});
};
}]);
So my expectation is that by "pushing" the new object into the existing array, the product list which references the "products" property would automagically update. I even update the Title property when the request is completed and that change is also not being displayed in the form.
I KNOW that the new product is being created in the databse and even that it's being returned ... in fact I have confirmed that the "self.products" property is even being updated.
I also considered that since a SERVICE is actually an instantiation that perhaps a different instance of the ProductService is being passed to each controller. I have eliminated this as a possibility since I can change the property, svc.Title, in one controller and see that it is propogated to the other controller ("Coffee" changes to "Tea").
Anyway ... it appears as though I am somehow losing the reference to the "self." properties when the AJAX request to add a new product is completed and I am not sure why that is. I would welcome any thoughts or guidance.
Thanks, -G
!! SOLUTION !!
Thanks to shaunhusain for the solution ... pretty simple resolution actually. I added a dependency on $rootScope to the service and in the success handler of the jQuery AJAX request I added a call to $apply to the $rootScope. That's it. :)
app.service('ProductService', ['$http', '$rootScope', function ($http, $rootScope) {
// ...
// all this stuff stayed the same
// ...
//
// calls web service to add a new product
self.addNewProduct = function () {
var formData = new FormData();
formData.append('ProductName', self.form.productName);
$.ajax({
type: "POST",
url: "/API/ProductAPI/AddUserProduct/36d43a01-22e5-494d-9e46-2d4ea5df7001",
data: formData,
processData: false,
contentType: false
})
.done(function (data) {
if (data != null) {
self.products.push(data);
self.Title = "Completed";
$rootScope.$apply(); // <-- Here's the fix ...
}
});
};
}]);