Kind of 2 questions in 1 here, Finding Angular really hard to debug.
Seems like stuff just magically breaks and fixes itself.
For example I have an ajax call to delete a "site".
Worked fine, I was happy with it so I decided to add a bit of code to splice it from the list. Now my id come through as null.
I put a break point in In google chrome for my onClick() but it never gets hit.
Using MVC5 and angular
HTML
<table class="table">
<tr>
<th>
Value
</th>
<th></th>
</tr>
<tr ng-repeat="site in sites">
<td>
{{site.Value}}
</td>
<td>
<button id="delete-{{site.Id}}" class="btn btn-primary" ng-click="deleteSite(site.id)">Delete</button>
</td>
</tr>
Angular controller
ngApp.controller('siteController', ['$scope', '$http', '$location', function ($scope, $http, $location) {
$http.post(ROOT + '/Site/LoadAll/')
.success(function (result) {
$scope.sites = result;
}).
error(function (data, status, headers, config) {
// log to console?
});
$scope.deleteSite = function (id) {
//$http.post(ROOT + 'SiteList/Delete/', JSON.stringify(id)) //null id
//$http.post(ROOT + 'SiteList/Delete/', id) //Invalid JSON primitive: 5f6d794f-bf13-4480-9afd-3b10d7b6ae32.
//$http.post(ROOT + 'Site/Delete/', { id: id } )
$http.post(ROOT + '/Site/Delete/' + id)
.success(function (result) {
// log to console?
}).
error(function (data, status, headers, config) {
// log to console?
});
for (var i = $scope.sites.lenght - 1; i >= 0; i--)
{
if ($scope.sites[i].id == id)
{
$scope.sites.splice(i,1)
}
}
//for(var i=0)
// find the $scope.sites that matches the id
// javascript re mpve that elemtne from the array
};}]);
Server controller
public JsonResult Delete(Guid id)
{
try
{
//var convertedID = new Guid(id);
_siteService.Delete(id);
return Json("OK", JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
return Json("Error" + e.Message);
}
}
So can anybody spot an issue here and how do you guys debug your Angular?
It seems there is a javascript, a http request and a asp.net mvc modelbinding flow to debug here.
In javascript i would add a few console.log statements. I use chrome for development and debugging. You could add a console.log('site', i, id, $scope.sites); in your "splice loop" to check if you get the expected result.
Http traffic can be monitored with chrome in the network tab or you can download fiddler2. Both will take a little playing around with to understand but once you get it, it's pretty straightforward. Pay attention to your form values in the request. It should hold a key id with the id value of the site.
In your asp.net mvc application you can set a breakpoint on your first { line of your action method and inspect the id variable to see if that holds the expected value.
Related
Within an ng-repeat block I have textboxes. To detect when the content differs from the original, the original data is stored in a variable.
<tr data-ng-repeat="p in products">
<td>
<textarea data-elastic data-ng-model="p.comment" data-ng-change="hasChanged=checkChange(original, rnd.comment);" data-ng-init="original=rnd.comment; hasChanged=false"></textarea>
</td>
<td class="save" ng-show="hasChanged" ng-click="save(p, original)">Save</td>
A save button is shown only when the content has changed. After a successful save the original value should be updated to the new value.
I can do it like this with a function in the controller:
$scope.save = function (p, original) {
//...successful save
this.original = p.comment; //this works
original = p.comment; //this does not
}
Relying on some implicit scope in the form of 'this' doesn't seem sensible.
Why doesn't updating the variable (original = ...) work? What's a smarter way to do this?
Based on comments I've updated it as follows:
ng-click="save(p, this)"
$scope.save = function (p, scope) {
//...successful save
scope.original = p.comment; //this works
}
This seems failrly sensible now. Is passing scope around like this considered bad practice or acceptable?
Products is defined as follows:
productStatusApp.controller('productStatusCtrl', function ($scope, $http, cid) {
$http.get('api/company/products/' + cid).success(function (data) {
$scope.products = data.products;
});
I've found the best way to avoid this kind of problems, is to use services
https://docs.angularjs.org/guide/services
http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
some rough code(use it just for pointers, not tested at all)
<tr data-ng-repeat="p in ProductsService.products">
<td>
<textarea data-elastic data-ng-model="p.comment"></textarea>
</td>
<td class="save" ng-show="p.originalComment!==p.comment" ng-click="ProductsService.save(p)">Save</td>
</tr>
and
var module = angular.module('app', []);
module.service('ProductsService', function () {
var products = [postA,postB,...,PostC];
products = products.map(function(p){p.originalComment=p.comment});
var save = function(p){
p.originalComment=p.comment;
someAjaxRequest(function _callback(err,response){....})
}
return {products:products,save:save};
});
and
module.controller('ProductsController', function ($scope, ProductsService) {
$scope.ProductsService= ProductsService;
});
They also allow better readability , WIN WIN
I am trying to pull content from my website through JSON. I have successfully called it and even tags are showing properly. The only issue I am facing is the list is not displaying.
Here is how my code looks like:
module.controller('FiveReasons', function($scope, $http, $rootScope, $sce) {
ons.ready(function() {
console.log("Inside 5 Reasons");
//$scope.spinner = true;
var reasonsListing = $http.get("http://vbought.com/design_14/index.php/design_ci/post/Clients");
reasonsListing.success(function(data, status, headers, config) {
console.log(data[0].post_title);
$scope.reasonsLists = data;
$scope.spinner = false;
});
reasonsListing.error(function(data, status, headers, config) {
alert("Can Not load the address Ajax");
});
});
});
I feel like list need to be refreshed after ng-repeat is completed. But I am not sure how can I do it in Angular.
Here is how I called this.
<ons-carousel swipeable overscrollable auto-scroll fullscreen var="carousel" name="FiveRes" class="FiveRes">
<ons-carousel-item style="background: #09a4c0;" ng-repeat="reasonsList in reasonsLists" bn-log-dom-creation="with">
<div class="item-label">Number</div>
</ons-carousel-item>
<ons-carousel-cover></ons-carousel-cover>
</ons-carousel>
I can't comment original posts yet so I'll just do this here. What does reasonsLists look like? Is your carousel displaying anything at all (looking at your code, it should display "Number" as many times as the length of reasonsLists)?
Otherwise, if you wanted to display, for instance, the post_title of each entries in reasonsLists instead of Number, then replace:
<div class="item-label">Number</div>
by:
<div class="item-label">{{reasonsList.post_title}}</div>
So i ran into a little problem and i am having a hard time understanding what I should do to get the result I want/need.
So my application is supposed to show the route a certain object made in the last 2 hours. When the user loads the app they see several points scattered through out the map and when they click on one of those objects the route it made in the last 2 hours is shown, and a table I have is supposed to be updated with those coordinates. Now I make the call to fill the partial view when I get all the locations the object went to in the controller method.
this is how I start all of this (when the user clicks a point the following is executed)
(I am using openlayers 3 but it is irrelevant to this question)
$.ajax({
type: "GET",
url: '/Controller/GetRoutes',
dataType: "json",
success: function (result) {
alert('Added');
var layerLines = new ol.layer.Vector({
source: new ol.source.Vector({
features: [
new ol.Feature({
geometry: new ol.geom.LineString(routes, 'XY'),
name: 'Line'
})
]
})
});
map.addLayer(layerLines);
},
error: function (xhr, ajaxOptions, thrownError) {
//some errror, some show err msg to user and log the error
alert(xhr.responseText);
}
});
So as you can see from this code the method GetRoutes() is going to be responsible for getting the information on where the object has been to.
This is the controller (I omitted most of the code thats responsible for drawing the actual routes since its quite a bit chunky)
[HttpGet]
public JsonResult GetRoutes()
{
var lastpoints = get an arraylist with all the points I want
var name = get the name of the object
RouteInformation(lastPoints,name);
return Json(lastPoints, JsonRequestBehavior.AllowGet);
}
I know I should probably change something here but i do not know what.
The method that gives me the last points is not mine, but I am required to use it so I have no other choice but to accept the arrayList it returns to me.
this is the RouteInformation method
public ActionResult RouteInformation(ArrayList routeList, string name )
{
List<ObjPoint> routes = routeList.OfType<ObjPoint>().ToList();
List<ObjRoute> objRoutes = new List<ObjRoute>();
objRoutes.Add(new ObjRoute()
{
name = name,
ObjPoints = routes
});
return PartialView("RouteDetailsView", objRoutes);
}
My issue is updating/refreshing that table, I have it in a partial view but I have no idea on what I have to do in order update that table with the information I want to display (i can get that information I just can´t seem to show it).
ObjPoint is composed of latitude,longitude, date, time.
This is the ObjRoute model
public class ObjRoute
{
public string name { get; set; }
public List<ObjPoint> ObjPoints { get; set; }
}
And now the Views ...
this is how the "main view" calls the partial view
<div>
#Html.Partial("routeDetailsView")
</div>
And this is my partial view
#model webVesselTracker.Models.ObjRoute
#{
ViewBag.Title = "RouteDetailsView";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>
<table id="routeTable" class="table">
<tr>
<th>
Name
</th>
<th>
Latitude
</th>
<th>
Longitude
</th>
<th></th>
</tr>
#if (Model != null) {
foreach (var item in Model.ObjPoints)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Latitude)
</td>
<td>
#Html.DisplayFor(modelItem => item.Longitude)
</td>
</tr>
}
}
else
{
<tr>
<td>
No object has been selected, please select one
</td>
</tr>
}
</table>
</div>
Now I know I could probably do this by adding some sort of json request in the js file and from there on build the table, but I would like to know how to do this with Razor and where I have gone wrong in the code/logic.
Am I supposed to add some ajax elsewhere?
So to summarize this:
-User sees points.
-User clicks point to see the route it made.
-App draws the route and then sends the route information to a method table so it can be added to the table
the user can see that information
Thank you for your time and if I missed something please point it out so I can fix or explain it better.
I finally gave up and looked into knockout.js, it managed to solve all the issues I was having
knockout.js
Problem Question,
I have a delete method on my cart, so when I press on delete button it delete the item of it. But my view is not getting update whereas item is getting successfully deleted.
//Cart controller
(function() {
var cartController = function($scope,cartService,NotificationService) {
getCart();
function getCart() {
cartService.getCart()
.success(function (cart) {
if (cart != null || cart != 'undefined') {
$scope.cartData = cart;
console.log(cart)
}
})
.error(function (status, data) {
console.log(status);
console.log(data);
})
};
$scope.deleteItem = function (productID) {
cartService.deleteCartItem(productID)
.success(function () {
NotificationService.add("success", "Item deleted from the cart", 3000);
getCart();
})
.error(function (status, data) {
console.log(status);
console.log(data);
})
}
cartController.$inject = ['$scope', 'cartService', 'NotificationService'];
angular.module('cartApp').controller('cartController', cartController);
}
}());
//my view
<div class="mainContainer" ng-controller="cartController">
<tr ng-repeat="cart in cartData.carts" >
<td>
<button ng-click="deleteItem(cart.id)" class="btn btn-primary">Delete</button>
</td>
</tr>
</div>
Please guide me what I am doing wrong.
Add $scope.$apply(); after the ajax call.
$scope.cartData = cart;
console.log(cart);
$scope.$apply();
When working with $scope in angular you will run into this issue when you overwrite an object on $scope (as opposed to modifying properties of that object). As previously mentioned $scope.$apply() will force Angular to reevaluate all your bindings and should cause your UI to update. That being said, making a call to your API to get the entire contents of the cart following a delete operation seems very inefficient here. You either want to send back an empty 200 OK and use that as a trigger to know that it is safe to remove the item client side using splice. The other option would be to send the new cart contents as the body of your 200 OK following a successful delete and at least save yourself an extra round trip.
Add a $scope.$apply() to the end of your call. This will run the angular digest loop and update your view.
Update the $scope.cartData array removing the deleted one on the success method
$scope.cartData.splice($scope.cartData.indexOf(productID), 1 );
I am trying to submit a form with multiple table rows. I have found examples online and have managed to send the table data to the AngularJS controller but I cannot figure out how to send that data to the apiController.
The form is a Purchase Order that has a table with the Purchase Order Details. I have chained the submit button with the both the Purchase Order and Purchase Order Detail submit functions.
<table class="table" style="">
<tbody>
<tr class="pointer no_selection" ng-repeat="newJobItem in rows">
<td style="width:50px"><input style="width:20px" type="checkbox" class="form-control"></td>
<td style="width:200px">{{newJobItem.JobItemName}}</td>
<td style="width:480px">{{newJobItem.JobItemDescription}}</td>
<td style="width:100px">{{newJobItem.JobItemMatSize}}</td>
<td style="width:150px">{{newJobItem.JobItemQuantity}}</td>
<td style="width:50px">{{newJobItem.JobItemUOM}}</td>
<td style="width:150px">${{newJobItem.JobItemPrice | number : fractionSize}}</td>
<td style="width:20px"><input type="button" value="X" class="btn btn-primary btn-sm" ng-click="removeRow(newJobItem.JobItemName)" /></td>
</tr>
</tbody>
</table>
<input style="margin-right:30px" id="btn-width" type="button" class="btn btn-default" ng-click="submitPurchaseOrder();submitPurchaseOrderDetail()" value="Submit"/>
Controller
//Post Purchase Order
$scope.PODate = new Date(); //Todays Date
$scope.POId = Math.floor(Math.random() * 1000000001) //PurchaseOrder Id Generator
$scope.submitPurchaseOrder = function () {;
var data = {
JobId: $scope.job.JobId,
POId : $scope.POId,
PONumber: $scope.currentItem.PONumber,
PODate: $scope.PODate,
POAmount: $scope.currentItem.POAmount,
POLastPrintDate: $scope.currentItem.POLastPrintDate,
POEmail: $scope.POEmail,
POPrint: $scope.currentItem.POPrint,
POFaxNumber: $scope.POFaxNumber,
PONotes: $scope.currentItem.PONotes,
POCreatedBy: $scope.currentItem.POCreatedBy,
PODeliveryDate: $scope.currentItem.PODeliveryDate,
POShipVia: $scope.currentItem.POShipVia,
POShowPrices: $scope.currentItem.POShowPrices,
POCostCode: $scope.currentItem.POCostCode,
POApprovedNumber: $scope.currentItem.POApprovedNumber,
POBackorder: $scope.currentItem.POBackorder,
}
$http.post('/api/apiPurchaseOrder/PostNewPurchaseOrder', data).success(function (data, status, headers) {
console.log(data);
var tmpCurrentItem = angular.copy($scope.currentItem);
$scope.purchaseOrderArray.push(tmpCurrentItem)
angular.copy({}, $scope.currentItem);
//hide modal window
$scope.openNewPurchaseOrderModal.then(function (m) {
m.modal('hide');
});
});
};
//Post Purchase Order Detail
$scope.newJobItem = {};
$scope.submitPurchaseOrderDetail = function () {
var index = 0;
$scope.rows.forEach(function (newJobItem) {
console.log('rows #' + (index++) + ': ' + JSON.stringify(newJobItem));
});
var data = {
POId: $scope.POId,
PODItem: $scope.newJobItem.JobItemName,
PODDescription: $scope.newJobItem.JobItemDescription,
PODNotes: $scope.PODNotes,
PODUOM: $scope.newJobItem.JobItemUOM,
PODPrice: $scope.newJobItem.JobItemPrice,
PODQuantity: $scope.newJobItem.JobItemQuantity,
PODAmount: $scope.PODAmount,
PODMatSize: $scope.newJobItem.JobItemMatSize,
PODSection: $scope.PODSection,
PODMultiplier: $scope.PODMultiplier,
PODBackOrder: $scope.PODBackOrder
}
$http.post('/api/apiPurchaseOrderDetail/PostNewPurchaseOrderDetail', data).success(function (data, status, headers) {
console.log(data); window.top.location.reload();
});
};
Purchase Order Detail ApiController
// POST api/<controller>
public async Task<IHttpActionResult> PostnewPurchaseOrderDetail([FromBody]PurchaseOrderDetail newPurchaseOrderDetail)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var context = new ApplicationDbContext())
{
context.PurchaseOrderDetails.Add(newPurchaseOrderDetail);
await context.SaveChangesAsync();
return CreatedAtRoute("PurchaseOrderDetailApi", new { newPurchaseOrderDetail.PODId }, newPurchaseOrderDetail);
}
}
Update
Changed as suggested
// POST api/<controller>
public HttpResponseMessage PostNewPurchaseOrderDetail(int id, PurchaseOrderDetail newPurchaseOrderDetail)
{
ApplicationDbContext db = new ApplicationDbContext();
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
if (id != newPurchaseOrderDetail.PODId)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
db.Entry(newPurchaseOrderDetail).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
You are executing two functions, each making an asyncronous request:
ng-click="submitPurchaseOrder();submitPurchaseOrderDetail()"
This doesn't feel right - both requests are sent in parallel and none is waiting for the other. Do you really mean it?
I would either package and send all data in a single request (also better experience for the user), then let the server deal with un-packaging. Or, if one request needs to wait for another, chain the Promises returned by $http:
$http.post(...)
.then(function(){
return $http.post(...);
})
.success(...)
.fail(...);
or use $q.all(promises) instead.
EDIT.
Also a cleaner and more scalable approach is to use dedicated Angular Service to post your data, see e.g. example on Angular Homepage
I'm not sure about your approach, to me it would be better to join both requests, purchaseOrder and the detail into a single call, you can still separate the data as you may feel convenient.
calling both functions at the same time could have unexpected behaviors since both are running asynchronously you may find 'race conditions'.
try to send a single request and organize how you are going to post the information like
var data = {
JobId: $scope.job.JobId, //common data between the order and the detail
POId: $scope.POId,
Order:{
PONumber: $scope.currentItem.PONumber,
PODate: $scope.PODate,
POAmount: $scope.currentItem.POAmount,
POLastPrintDate: $scope.currentItem.POLastPrintDate,
POEmail: $scope.POEmail,
POPrint: $scope.currentItem.POPrint,
POFaxNumber: $scope.POFaxNumber,
PONotes: $scope.currentItem.PONotes,
POCreatedBy: $scope.currentItem.POCreatedBy,
PODeliveryDate: $scope.currentItem.PODeliveryDate,
POShipVia: $scope.currentItem.POShipVia,
POShowPrices: $scope.currentItem.POShowPrices,
POCostCode: $scope.currentItem.POCostCode,
POApprovedNumber: $scope.currentItem.POApprovedNumber,
POBackorder: $scope.currentItem.POBackorder,
},
Detail:{
PODItem: $scope.newJobItem.JobItemName,
PODDescription: $scope.newJobItem.JobItemDescription,
PODNotes: $scope.PODNotes,
PODUOM: $scope.newJobItem.JobItemUOM,
PODPrice: $scope.newJobItem.JobItemPrice,
PODQuantity: $scope.newJobItem.JobItemQuantity,
PODAmount: $scope.PODAmount,
PODMatSize: $scope.newJobItem.JobItemMatSize,
PODSection: $scope.PODSection,
PODMultiplier: $scope.PODMultiplier,
PODBackOrder: $scope.PODBackOrder
}
}
you'll need to map the purchase order and the detail to a single dto object in your api controller.
just as a sidenote, I'd recommend you to use a stronger unique identifier rather than using a random number.
I think the trouble is with you controller try using this and also can you please elaborate the trouble.Does your APIcontroller is never found by your controller ?
public class Oders : ApiController // in your controller class
{
public HttpResponseMessage Post(PurchaseOrderDetails newOrder)
{
//your code
}
}