updating model and binding update operations with UI - angularjs

I currently have developed a table of content using AngularJS, the table will populate based on an Angular Service "Model" which invokes a web service and returns list and using ng-repeat and creating a table and all its content.
Everything at the moment works fine, I have a minor problem though. Part of the table, we are outputting an action button which when clicked invokes a web service which update the current record. Am trying to make the record data gets updated automatically, but i must refresh the page in order to see the changes.
Here is my code
My app.js
angular.module('my_vehicles', ['vehicleServices', 'AccountsDirectives']);
service.js
'use strict';
angular.module('vehicleServices', ['ngResource']).
factory('Car', function($resource) {
return $resource('/vehicle/api/car.json/:id', {},
{
query: {method:'GET', isArray:false},
delete: {method:'DELETE', isArray:false},
update: {method:'PUT', isArray:false}
}
);
});
controller.js
'use strict';
function MyVehicleController($scope, Car) {
var init = function() {
$scope.page_has_next = true;
$scope.cars = [];
$scope.page = 1;
};
// initialize values
init();
Car.query({my_vehicle: true},
// success
function(data) {
$scope.page_has_next = data.has_next;
$scope.cars = data.objects;
},
// error
function(data) {
}
);
$scope.mark_sold = function(id, index) {
Car.update({
id : id,
status : 'S'
},
function(data) {
});
}
$scope.delete = function(id, index) {
Car.delete(
{id: id},
// on success
function() {
// remove the element from cars array and it will be
// automatically updated by ng-repeat
$scope.cars.splice(index, 1);
$scope.loadMore(1);
}
);
}
$scope.is_total_zero = function() {
return !!($scope.cars.length)
//return $scope.cars.length > 0 ? true : false
}
$scope.loadMore = function(limit) {
if($scope.page_has_next) {
$scope.$broadcast('loading_started');
console.log(limit);
Car.query({my_vehicle: true, page: $scope.page, limit: limit},
// success
function(data) {
$scope.page_has_next = data.has_next;
$scope.cars = $scope.cars.concat(angular.fromJson(data.objects));
$scope.page++;
$scope.$broadcast('loading_ended');
},
// error
function() {
$scope.page_has_next = false;
$scope.$broadcast('loading_ended');
}
);
}
}
$scope.$on('loading_started', function() {
$scope.state = 'loading';
});
$scope.$on('loading_ended', function() {
$scope.state = 'ready';
});
}
and finally, my html code
<tr ng-repeat="car in cars">
<td>{% ng car._get_model_display.make_display %} {% ng car._get_model_display.model_display %} {% ng car._get_model_display.trim_display %}</td>
<td>{% ng car.created_at_format %}</td>
<td>{% ng car.view_count %}</td>
<td ng-model="car.status_label">{% ng car.status_label %}</td>
<td>
<div class="btn-group">
<button ng-disabled="car.status == 'S' || car.status == 'P'" ng-model="edit" class="btn btn-mini edit-btn">{% trans 'Edit' %}</button>
<button ng-disabled="car.status == 'S'" ng-click="delete(car.id, $index)" class="btn btn-mini delete-btn">{% trans 'Delete' %}</button>
<button ng-disabled="car.status == 'S' || car.status == 'P'" ng-click="mark_sold(car.id, $index)" class="btn btn-mini edit-btn">{% trans 'Mark as sold' %}</button>
</div>
</td>
</tr>
P.S the {% ng XXX %} is outputting {{ XXX }}, am using the above syntax because django templating engine does not allow me to use {{}} so i've developed a templatetag that would output {{}} ..
As mentioned earlier, my problem is that every time I invoke "mark as sold" it would invoke the cars.update() but it will not update the record displayed, must refresh to see changes. Any idea how i can solve this?

As far as I understand your code you only update the db without updating the cars model ($scope.cars) so changes are only reflected in the db but not in the AngularJS application.
Maybe try the following:
$scope.mark_sold = function(id, index) {
Car.update({
id : id,
status : 'S'
},
function(data) {
$scope.cars[id].status = 'S';
});
}

You need to also update your in-memory cars array.
You already have the array index (second parameter of the mark_sold function):
$scope.mark_sold = function(id, index) {
Car.update({
id : id,
status : 'S'
},
function(data) {
// Update in-memory array
$scope.$apply(function(scope) {
scope.cars[index].status = 'S';
});
});
}

Related

Angular - Trying to access value outside of $http get success and use it for a filter value

I'm building a silly little Football app. On the first page, I am trying to load the country's top division standings and the coming week's fixtures.
I retrieve the data, using a RESTful Web Service and so is done asynchronously. The table is fine, but not the fixtures.
There is an array of fixture objects, within them, there's a 'matchday' and 'status' property. If you look at the 'this.getFixtures' function, look at the success code block. What I am trying to do is only display the fixtures for a certain match day. If there is one game left to be played on a certain matchday, then I only want that fixture displayed. If not, display next matchday's fixtures.
The 'status' property typically has a value of 'SCHEDULED' or 'FINISHED'. In the success code block I am saying:
Loop through all fixtures retrieved.
If this fixture is scheduled, that means, we're on the matchday for this fixture.
In which case, break loop.
I am then trying to use that value outside the get method, but I keep getting undefined. Is there any way to access that value outside the success block?
I'll use the $scope.matchDay function as the filter.This will help me to only display scheduled fixtures in that matchday with ng-repeat.
Anyway, sorry for the long winded post, but here's the code:
HTML:
<div class="grid-x">
<div class="medium-8 medium-offset-2 cell">
<div id="premier-league-banner">
<div class="banner-shade">
<div class="grid-x">
<div class="medium-5 cell">
<table>
<tr ng-repeat="team in premierLeagueTable.standing | limitTo: 6">
<th>{{ $index + 1 }}</th>
<td><img class="prem-thumbnail" src="{{ team.crestURI }}" /></td>
<th>{{ team.teamName }}</th>
<th>{{ team.playedGames }}</th>
<th>{{ team.goalDifference }}</th>
<th>{{ team.points }}</th>
</tr>
</table>
</div>
<div class="medium-2 cell">
<img src="images/prem-logo.png" />
</div>
<div class="medium-5 cell">
<table>
<tr ng-repeat="fixture in premierLeagueFixtures.fixtures | filter:{matchday: 10}">
<th>{{fixture.homeTeamName}}</th>
<td>vs</td>
<th>{{fixture.awayTeamName}}</th>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
Angular JS
// MODULE
var quickEleven = angular.module('quickEleven', ['ngRoute', 'ngResource']);
// ROUTES
quickEleven.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'pages/home.htm',
controller: 'homeController'
})
});
// CONTROLLERS
quickEleven.controller('homeController', ['$scope', '$resource', '$log', 'footballData', function($scope, $resource, $log, footballData) {
function getMonday(date) {
var day = date.getDay() || 7;
if( day !== 1 )
date.setHours(-24 * (day - 1));
return date;
}
function convertDate(date) {
var yyyy = date.getFullYear().toString();
var mm = (date.getMonth()+1).toString();
var dd = date.getDate().toString();
var mmChars = mm.split('');
var ddChars = dd.split('');
return yyyy + '-' + (mmChars[1]?mm:"0"+mmChars[0]) + '-' + (ddChars[1]?dd:"0"+ddChars[0]);
}
var thisMonday = getMonday(new Date);
var nextMonday = getMonday(new Date);
nextMonday.setDate(nextMonday.getDate() + 7);
$log.info("Boom! " + convertDate(thisMonday));
$log.info("For! " + convertDate(nextMonday));
$scope.premierLeagueTable = footballData.getLeagueTable("http://api.football-data.org/v1/competitions/:competitionId/leagueTable", 445);
//http://api.football-data.org/v1/competitions/:competitionId/fixtures?timeFrameStart=2018-03-01&timeFrameEnd=2018-03-05
//"http://api.football-data.org/v1/competitions/:competitionId/fixtures/?matchday=9"
$scope.premierLeagueFixtures = footballData.getFixtures("http://api.football-data.org/v1/competitions/:competitionId/fixtures?timeFrameStart=" + convertDate(thisMonday) + "&timeFrameEnd=" + convertDate(nextMonday), 445);
$log.info($scope.premierLeagueFixtures);
$log.info($scope.premierLeagueTable);
$scope.matchdayValue = 9;
$scope.matchDay = function() {
return footballData.getMatchday();
};
}]);
quickEleven.service('footballData', ['$resource', '$log', function($resource, $log) {
//Referring to the latest matchday with the status as 'SCHEDULED'
var self = this;
var test;
self.latestScheduledMatchday = 0;
self.getMatchday = function() {
$log.info("This is: " + test);
return self.latestScheduledMatchday;
}
this.getLeagueTable = function (footballUrl, compId) {
this.footballAPI =
$resource(footballUrl, {}, {
get: {
method: "GET",
headers: {
"X-Auth-Token": "f73808b698e84dccbe4886da3ea6e755"
}
}
})
.get({
competitionId: compId
}, function(data) {
this.fussball = data;
}, function(err) {
$log.error(err);
});
return this.footballAPI;
};
this.getFixtures = function (footballUrl, compId) {
// var self;
this.footballAPI =
$resource(footballUrl, {}, {
get: {
method: "GET",
headers: {
"X-Auth-Token": "f73808b698e84dccbe4886da3ea6e755"
}
}
})
.get({
competitionId: compId
}, function(data) {
// self = data.fixtures;
self.latestScheduledMatchday = data.fixtures[0].matchday
for (var i = 0; i < data.fixtures.length; i++) {
var fixture = data.fixtures[i];
if (fixture.status == 'SCHEDULED') {
test = fixture.matchday;
break;
}
}
$log.info("Dollar signs... " + test);
}, function(err) {
$log.error(err);
});
return this.footballAPI;
};
}]);
I see 2 issues so far. One on the note of undefined values is your service might not be getting implemented correctly. AFAIK you should be returning the service in the "function($resource, $log) {" function.
Here's how I'd change it (note I've not tested this)
quickEleven.service('footballData', ['$resource', '$log', function($resource, $log) {
//Referring to the latest matchday with the status as 'SCHEDULED'
var wrappedService = {};
var test;
var latestScheduledMatchday = 0;
var getMatchday = function() {
$log.info("This is: " + test);
return latestScheduledMatchday;
}
wrappedService.getLeagueTable = function (footballUrl, compId) {
wrappedService.footballAPI =
$resource(footballUrl, {}, {
get: {
method: "GET",
headers: {
"X-Auth-Token": "f73808b698e84dccbe4886da3ea6e755"
}
}
})
.get({
competitionId: compId
}, function(data) {
wrappedService.fussball = data;
}, function(err) {
$log.error(err);
});
return wrappedService.footballAPI;
};
wrappedService.getFixtures = function (footballUrl, compId) {
wrappedService.footballAPI =
$resource(footballUrl, {}, {
get: {
method: "GET",
headers: {
"X-Auth-Token": "f73808b698e84dccbe4886da3ea6e755"
}
}
})
.get({
competitionId: compId
}, function(data) {
latestScheduledMatchday = data.fixtures[0].matchday
for (var i = 0; i < data.fixtures.length; i++) {
var fixture = data.fixtures[i];
if (fixture.status == 'SCHEDULED') {
test = fixture.matchday;
break;
}
}
$log.info("Dollar signs... " + test);
}, function(err) {
$log.error(err);
});
return wrappedService.footballAPI;
};
return wrappedService;
}]);
So instead of the function returning no service, you have your service wrapped and returned as I believe you were intending. I also removed references to "self" since your intention there (internal service variables) is more eloquently handled with var scoping in the function.
Second issue that you will see once your service is working.
$scope.premierLeagueTable = footballData.getLeagueTable("http://api.football-data.org/v1/competitions/:competitionId/leagueTable", 445);
This line does not return the request data, but returns the request object. In fact by the time $scope.premierLeagueTable is set, the request hasn't even completed yet. What you do get is access to a promise that you can put a callback function in. See the angular resource documentation for more info, specifically the third example in the user-resource section where you see .$promise https://docs.angularjs.org/api/ngResource/service/$resource#user-resource.
Whatever functionality you want to apply the data return to should live inside that .$promise.then(...) callback. I'm not entirely sure if the promise in there receives the response data, or your callback return. You'll have to read further or experiment to find that out.

How To Update Page After Calling $http.put service in Directive Using AngularJS/Web API/Angular-ui-router

We are new to AngularJS but are working on an AngularJS/Web API application that updates a data model from an AngularJS Bootstrap popover/directive.
We've successfully updated the database from the directive/popover, however are having trouble figuring out how to refresh the data on the page with the updated data without reloading the page.
Main Page CSHTML:
<div ng-app="FFPA" ng-controller="myCtrl">
<div svg-floorplan="dataset"></div>
</div>
Popover HTML:
<div>
<div>
ID: {{ person.Id }}<br />
Name: {{ person.fullName }}<br />
Current Cube/Office: {{ person.seatId }}
<br />
Dept: {{ person.deptId }}
<br />
Job Desc: {{ person.jobDesc}}
<br />
Phone:{{ person.phone}}
<br />
<!--<input type="button" value="Click Me" ng-click="changeName()">-->
</div>
<div class="hiddenDiv" ng-hide="toggle">
<div class="form-group">
<label for="floor">Floor</label>
<input id="floor" ng-model="person.floor" type="text" ng-trim="true" class="form-control" />
</div>
<div class="form-group">
<label for="section">Section</label>
<input id="section" ng-model="person.section" ng-trim="true" type="text" class="form-control" />
</div>
<div class="form-group">
<label for="offCubeNum">offCubeNum</label>
<input id="offCubeNum" ng-model="person.offCubeNum" ng-trim="true" type="text" class="form-control" />
</div>
<div class="form-group">
<label for="cbCube">Cubicle?</label>
<input id="cbCube" ng-model="person.cbCube" type="checkbox" size="1" class="checkbox" />
</div>
</div>
<div ng-hide="buttonToggle">
<input type="button" value="Move" class="btn btn-success" ng-click="moveEmp()">
<input type="button" value="Term" class="btn btn-danger" ng-click="changeName()">
</div>
<div ng-hide="submitToggle">
<input type="button" value="Submit" class="btn btn-success" ng-click="submitMove()">
<input type="button" value="Cancel" class="btn btn-warning" ng-click="cancel()">
</div>
</div>
The main page initially gets data from a service in the angular controller:
var app = angular.module('FFPA', ['ngAnimate', 'ngSanitize', 'ui.bootstrap', 'ui.router']);
app.controller('myCtrl', function ($scope, dataService) {
$scope.test = 'test';
dataService.getData().then(function (data) {
//The reduce() method reduces the array to a single value.
$scope.dataset = data.reduce(function (obj, item) {
obj[item.seatId.trim()] = item;
item.fullName = item.fName + ' ' + item.lName;
item.deptId = item.deptId;
item.jobDesc = item.jobDesc;
item.phone = item.phone;
return obj;
}, {});
});
});
Get Data Service:
angular.module('FFPA').service('dataService', function ($http) {
this.getData = function () {
//web api call
return $http.get("api/Controller/GetData).then(
function (response) {
return response.data;
}, function () {
return { err: "could not get data" };
}
);
}
});
The Update Service is called from the Popover Directive.
Update Service:
angular.module('FFPA').service('updateService', function ($http) {
this.putData = function (oc) {
//web api call
return $http.put("api/Controller/PutUpdateData", oc).then(
function (response) {
return response.data;
}, function () {
return { err: "could not update data" };
}
);
}
});
Here is a snippet from our Popover directive where the update occurs and where we thought we could refresh the scope, and the data for the page:
updateService.putData(data).then(function (response) {
if (response == false)
alert("Move Failed!");
else {
alert("Move Succeeded.");
//$window.location.reload() causes a page reload..not desirable
//$window.location.reload();
$state.reload();
}
});
We tried a $state.reload(); in the popover directive just after updateService.putData(data), however this caused -> Error: Cannot transition to abstract state '[object Object]' error.
Here is the full Popover Directive:
angular.module('FFPA').directive('svgFloorplanPopover', ['$compile', 'updateService', 'vacancyService', 'addService', 'terminateService', '$window', '$state', function ($compile, updateService, vacancyService, addService, terminateService, $window, $state) {
return {
restrict: 'A',
scope: {
'person': '=svgFloorplanPopover',
//UPDATE 8-MAY-2017
onDataUpdate: '&'
},
link: function (scope, element, attrs) {
scope.moveToggle = true; //hide move toggle
scope.addToggle = true; //hide add toggle
scope.submitToggle = true; //hide submit toggle
scope.$watch("person", function () {
if (scope.person) {
if (scope.person.vacant == true) {
scope.addToggle = false; //show add button
scope.empInfoToggle = true; //hide emp info
}
else
scope.moveToggle = false; //show move
}
});
//add employee---------------------------------------------------------
scope.addEmp = function () {
scope.addToggle = scope.addToggle === false ? true : false;
scope.buttonToggle = true;
scope.submitToggle = false;
var data = {
deptId: scope.person.deptId,
divisionId: scope.person.divisionId,
empId: scope.person.empId,
floor: scope.person.floor,
fName: scope.person.fName,
lName: scope.person.lName,
jobDesc: scope.person.jobDesc,
officeCode: scope.person.officeCode,
phone: scope.person.phone,
section: scope.person.section,
seat: scope.person.seat,
seatId: scope.person.seatId,
seatTypeId: scope.person.seatTypeId,
vacant: scope.person.vacant
};
//call to update/move the employee
//updateService.putData(scope.person).then(function () {
addService.putData(data).then(function (response) {
if (response == false)
alert("Create Failed!");
else {
alert("Create Succeeded.");
//UPDATE 8-MAY-2017
$scope.onDataUpdate({ person: $scope.person, moreData: $scope.moreData });
//$window.location.reload();
//$route.reload();
//scope.toggle = false;
}
});
}
//cancel function---------------------------------------------------------
scope.cancel = function () {}
//Term emp---------------------------------------------------------
scope.termEmp = function () {
var data = {
seatId: scope.person.seatId,
floor: scope.person.floor
};
terminateService.putData(data).then(function (response) {
if (response == false)
alert("Term Failed!");
else {
alert("Term Succeeded.");
$window.location.reload();
//$route.reload();
//scope.toggle = false;
}
});
}
//move employee---------------------------------------------------------
scope.moveEmp = function () {
scope.toggle = scope.toggle === false ? true : false;
scope.buttonToggle = true;
scope.submitToggle = false;
if (scope.person && scope.person.fullName.indexOf('changed') === -1) {
//scope.person.fullName += ' move?';
}
//Json object to send to controller to check for vacancy
var data = {
floor: scope.person.floor,
section: scope.person.section,
seat: scope.person.offCubeNum
};
//can't send object via $http.get (?) stringigy json and cast to Office object in controller.
var json = JSON.stringify(data);
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//CHECK VACANCY service call
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
vacancyService.getData(json)
.then(function (response) {
if (response == false)
alert("cube/office occupied");
else{
//+++++++++++++++++++++++++++++++++++++++++++
//UPDATE service call
//+++++++++++++++++++++++++++++++++++++++++++
//CONSTS
var CONSTFLOORPREFIX = "f";
var CONSTSEAT = "s";
var CONSTC = "c"
var floor = scope.person.floor;
var section = scope.person.section;
var offCube = scope.person.offCubeNum;
scope.person.oldSeatId = scope.person.seatId;
var newOfficeId = CONSTFLOORPREFIX + floor + CONSTSEAT; //f3s
//IF CUBE
if (scope.person.cbCube) {
var trimSection = section.trim();
newOfficeId += trimSection + CONSTC; //f3s313c
var trimOffCube = offCube.trim();
newOfficeId += trimOffCube;
}
else {
newOfficeId += 0 + CONSTC + section; //f3s0c
}
scope.person.seatId = newOfficeId;
//Json object to send to controller to check for vacancy
var data = {
Id: scope.person.Id,
seatId: scope.person.seatId,
oldSeatId: scope.person.oldSeatId,
empId: scope.person.empId,
lName: scope.person.lName,
fName: scope.person.fName,
refacName: scope.person.refacName,
deptId: scope.person.deptId,
divisionId: scope.person.divisionId,
jobDesc: scope.person.jobDesc,
seatTypeId: scope.person.seatTypeId,
officeCode: scope.person.officeCode,
phone: scope.person.phone,
floor: scope.person.floor,
section: scope.person.section,
seat: scope.person.seat,
vacant: scope.person.vacant
};
//call to update/move the employee
//updateService.putData(scope.person).then(function () {
updateService.putData(data).then(function (response) {
if (response == false)
alert("Move Failed!");
else {
alert("Move Succeeded.");
//$window.location.reload();
$state.reload();
//$route.reload();
//scope.toggle = false;
}
});
}//end else
});
}
if (element[0].querySelector('text') != null){
scope.htmlPopover = './HTML/popoverTemplate.html';
element[0].setAttribute('uib-popover-template', "htmlPopover");
element[0].setAttribute('popover-append-to-body', 'true');
element[0].setAttribute('popover-trigger', "'click'");
//element[0].setAttribute('popover-trigger', "'mouseenter'");
element[0].setAttribute('popover-placement', 'right');
element[0].removeAttribute('svg-floorplan-popover');
$compile(element)(scope);
}
}
}
}]);
UPDATED: 8-MAY-2017
Originally there is one additional data service and a directive that we left out of this post since it may be considered not essential information, however recently added since it may be needed.
SVG Load Directive:
angular.module('FFPA').directive('svgFloorplan', ['$compile', function ($compile) {
return {
restrict: 'A', //restrict attributes
templateUrl: './SVG/HQ3RD-FLOOR3v10.svg',
scope: {
'dataset': '=svgFloorplan'
},
link: {
pre: function (scope, element, attrs) {
//filter groups based on a cube/office id
var groups = element[0].querySelectorAll("g[id^='f3']");
//groups.style("pointer-events", "all");
scope.changeName = function (groupId) {
if (scope.dataset[groupId] && scope.dataset[groupId].lastName.indexOf('changed') === -1) {
scope.dataset[groupId].lastName += ' changed';
}
}
groups.forEach(function (group) {
var groupId = group.getAttribute('id');
if (groupId) {
//set vacancy colors on vacant cubes
scope.$watch("dataset", function () {
if (scope.dataset) {
if (typeof scope.dataset[groupId] !== "undefined") {
//vacant cubes and offices hover
if (scope.dataset[groupId].vacant == true) {
//seat type id 1 = cube
if (scope.dataset[groupId].seatTypeId == 1){
d3.select(group).select("rect").style("fill", "#99ff33").style("opacity", 0.4)
.style("pointer-events", "all")
.on('mouseover', function () {
d3.select(this).style('opacity', 0.9);
})
.on('mouseout', function () {
d3.select(this).style('opacity', 0.4);
})
}
//vacant office
else {
d3.select(group).select("path").style("stroke", "#ffffff").style("opacity", 1.0);
d3.select(group).select("path").style("fill", "#99ff33").style("opacity", 0.4)
.style("pointer-events", "all")
.on('mouseover', function () {
d3.select(this).style('opacity', 0.9);
})
.on('mouseout', function () {
d3.select(this).style('opacity', 0.4);
})
}
}
else { //Occupied
//seat type id 1 = cube
if (scope.dataset[groupId].seatTypeId == 1) {
d3.select(group).select("rect").style("fill", "#30445d").style("opacity", 0.0)
.style("pointer-events", "all")
.on('mouseover', function () {
d3.select(this).style('opacity', 1.0);
d3.select(group).select('text').style("fill", "#FFFFFF");
})
.on('mouseout', function () {
d3.select(this).style('opacity', 0.0);
d3.select(group).select('text').style("fill", "#000000");
})
//TODO: cubes have rects and on the north side of the building wall, paths.
d3.select(group).select("path").style("fill", "#30445d").style("opacity", 0.0)
.style("pointer-events", "all")
.on('mouseover', function () {
d3.select(this).style('opacity', 1.0);
d3.select(group).select('text').style("fill", "#FFFFFF");
})
.on('mouseout', function () {
d3.select(this).style('opacity', 0.0);
d3.select(group).select('text').style("fill", "#000000");
})
}
//occupied office
else {
//d3.select(group).select("path").style("stroke", "#ffffff").style("opacity", 0.8);
d3.select(group).select("path").style("fill", "#5A8CC9").style("opacity", 1.0)
.style("pointer-events", "all")
.on('mouseover', function () {
//alert("office");
d3.select(this).style("fill", "#2d4768").style('opacity', 1.0);
d3.select(group).selectAll('text').style("fill", "#FFFFFF");
})
.on('mouseout', function () {
d3.select(this).style("fill", "#5A8CC9").style('opacity', 1.0);
d3.select(group).selectAll('text').style("fill", "#000000");
})
}
}//end occupied else
}
}
});
//UPDATE 8-MAY-2017->Implementation Question
scope.onDataUpdateInController = function (person, moreData) { };
var datasetBinding = "dataset['" + groupId + "']";
group.setAttribute('svg-floorplan-popover', datasetBinding);
//UPDATE 8-MAY-2017
//on-data-update corresponds to onDataUpdate item on svgFloorplanPopover's scope.
group.setAttribute('on-data-update', onDataUpdateInController);
$compile(group)(scope);
}
});
}
}
}
}]);
Vacancy Service (check before update):
angular.module('FFPA').service('vacancyService', function ($http) {
...
}
The main question is:
How can we have our application refresh our page with the updated data without reloading the page?
We used to be able to do this in UpdatePanels in ASP.Net webforms back in the day. I think they were partial postbacks/AJAX calls..
EDITED 2-AUG-2017
+++++++++++++++++++++++++++++++++++
Even though the bounty was automatically awarded, we still don't have an answer to this question. Without any implementation context the answers given are not useful.
Can anyone expand on the answers given to give us an idea on how this problem can be solved?
Thanks
Just add your data on $scope object and use it in your view, whenever you update or modify the data just
eg: consider you have a function to get the data where you are making rest call to your db
$scope.getdata=function(){
$http.get(url).success(function(data)
{ $scope.data=data;
});
Whenever you modify your data just call this function in your case on click of close of directive/popup
To refresh your view (not bind the received data) use the answers for the following questions:
Using ngRoute Module
How to reload or re-render the entire page using AngularJS
Using ui-router Module
Reloading current state - refresh data
With that I would recommend you to assign the received data to your bounded $scope property.
I'll add a full example after you'll provide an updated plnkr :)
Please try the following steps:
1. Create a method in svgFloorplanPopover directive and call it by passing in the data
In your svgFloorplanPopover directive, add onDataUpdate item in the scope
declaration:
...
scope: {
'person': '=svgFloorplanPopover',
onDataUpdate: '&'
}
...
and where you are trying to reload state, instead of reloading the state or page, call the below code. This is to create an event system which is fired from within the directive to let the controller or parent directive know that data has changed and view can now be updated.
$scope.onDataUpdate({person: $scope.person, moreData: $scope.moreData});
2. Create a method in svgFloorplan to accept the passed data
Since you are using nested directive approach, you'll need to use the below code in svgFloorplan directive.
group.setAttribute('svg-floorplan-popover', datasetBinding);
group.setAttribute('on-data-update', onDataUpdateInController);
on-data-update corresponds to onDataUpdate item on svgFloorplanPopover's scope.
Declare onDataUpdateInController method on the scope of svgFloorplan directive.
scope.onDataUpdateInController = function(person, moreData) {};
The object properties that you pass from within the directive are laid out flat to the number of parameters.
If you need to pass this data further up to your controller where svgFloorplan is declared. Repeat the above two steps for svgFloorplan directive.
I hope this approach is clear. It is no different than what is explained in Angular Directives, section Creating a Directive that Wraps Other Elements and code where a close button is added. Here is the direct link to the code in plunkr.
Just a question: Are you going to use these directives separately from each other? If no, you may try to create one directive instead of making them two. This will reduce complexity.

AngularFire $remove item from Array using a variable in Firebase reference does not work

I've been struggling with the following problem:
I'm trying to delete a 'Post' item from a Firebase Array with the $remove AngularFire method which I have implemented in a Angular Service (Factory). This Post is a child of 'Event', so in order to delete it I have to pass this Service a argument with the relevant Event of which I want to delete the post.
This is my controller:
app.controller('EventSignupController', function ($scope, $routeParams, EventService, AuthService) {
// Load the selected event with firebase through the eventservice
$scope.selectedEvent = EventService.events.get($routeParams.eventId);
// get user settings
$scope.user = AuthService.user;
$scope.signedIn = AuthService.signedIn;
// Message functionality
$scope.posts = EventService.posts.all($scope.selectedEvent.$id);
$scope.post = {
message: ''
};
$scope.addPost = function (){
$scope.post.creator = $scope.user.profile.username;
$scope.post.creatorUID = $scope.user.uid;
EventService.posts.createPost($scope.selectedEvent.$id, $scope.post);
};
$scope.deletePost = function(post){
EventService.posts.deletePost($scope.selectedEvent.$id, post);
// workaround for eventService bug:
// $scope.posts.$remove(post);
};
});
And this is my Service (Factory):
app.factory('EventService', function ($firebase, FIREBASE_URL) {
var ref = new Firebase(FIREBASE_URL);
var events = $firebase(ref.child('events')).$asArray();
var EventService = {
events: {
all: events,
create: function (event) {
return events.$add(event);
},
get: function (eventId) {
return $firebase(ref.child('events').child(eventId)).$asObject();
},
delete: function (event) {
return events.$remove(event);
}
},
posts: {
all: function(eventId){
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts;
},
createPost: function (eventId, post) {
// this does work
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts.$add(post);
},
deletePost: function (eventId, post) {
// this does not work
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts.$remove(post);
}
}
};
return EventService;
});
When I try to delete the link tag just freezes and no error logging appears in the console. While if I call $remove on my $scope.posts directly in my controller it magically works.. Furthermore my Post is not removed from my Firebase DB.
Another weird thing is that 'CreatePost' works perfectly fine using the same construction.
My view:
<div class="col-xs-8 col-xs-offset-2 well">
<form ng-submit="addPost()" ng-show="signedIn()">
<input type="text" ng-model="post.message" />
<button type="submit" class="btn btn-primary btn-sm">Add Post</button>
</form>
<br>
<div class="post row" ng-repeat="post in posts">
<div>
<div class="info">
{{ post.message }}
</div>
<div>
<span>submitted by {{ post.creator }}</span>
delete
</div>
<br>
</div>
</div>
</div>
P.s. I'm not too sure that my 'Service' is implemented in the best possible way.. I couldn't find another solution for doing multiple firebase calls
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
within the Post part of my EventService, because it depends on eventId in each CRUD operation. Any ideas would be very welcome :)
The easiest way for me was to use this:
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
ref.child(postId).remove(function(error){
if (error) {
console.log("Error:", error);
} else {
console.log("Removed successfully!");
}
});
The only way I'm able to remove the item is using a loop on the array we get from firebase.
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
var arr_ref=$firebaseArray(ref);
for(var i=0;i<arr_ref.length;i++){
if(key==arr_ref[i].$id){
console.log(arr_ref[i]);
arr_ref.$remove(i);
}
}

AngularJS - $rootScope.$apply not causing to refresh ng-model directives fed from descendant scopes

I have a function which is wrapped in $rootScope.$apply() by the wrapping call (SDK.api):
SDK.api('/me', function(response) {
//THIS callback, anonymous function, is wrapped by SDK in $rootScope.$apply
$scope.form = {
'first_name' : response.first_name,
'last_name' : response.last_name,
'email' : response.email
};
$scope.formEnabled = true;
$scope.formFetching = false;
});
Actually, this is a wrapper to Facebook JS SDK (and NOT the plain FB object) and such function is called in the context of $rootScope.$apply(). I can ensure that because using $scope.$apply() at the end of such function raises an "inprog" error (i.e. I cannot call $apply inside a call to $apply).
This $scope object in the code (and so: the code chunk I wrote here) belongs to a controller I created for an ngDialog plugin. The ng-dialog looks like this:
return ngDialog.open({
template: url + 'partials/dialog-form.html',
className: 'ngdialog-theme-plain',
scope: $scope,
closeByDocument: true,
closeByEscape: true,
showClose: true,
cache: false,
controller: ['$scope', '$http', function($scope, $http) {
/* ... more ... */
$scope.formFetching = true;
$scope.formEnabled = false;
$scope.success = false;
SDK.api('/me', function(response) {
$scope.form = {
'first_name' : response.first_name,
'last_name' : response.last_name,
'email' : response.email
};
$scope.formEnabled = true;
$scope.formFetching = false;
});
/* ... more ... */
}]
})
and the $scope in scope: $scope is the scope from the main controller (My app has only one controller - it's not too big).
So we could say: $rootScope is parent of $scope in main controller, which at the same time is parent of $scope of the ngDialog's $scope.
In the grandchild $scope, the form data is updated:
$scope.form = {
'first_name' : response.first_name,
'last_name' : response.last_name,
'email' : response.email
};
And there's the template url + 'partials/dialog-form.html' which actually exists and gets rendered. The content is as follows (I will omit irrelevant code):
<div id="pedido">
<form novalidate ng-submit="submitForm()">
<!-- more code -->
<table width="100%">
<!-- more code -->
<tbody>
<tr>
<td>Nombre:</td>
<td>
<input type="text" ng-model="form.first_name" />
<span ng-repeat="error in errors.first_name" class="error">{{ error }}</span>
</td>
</tr>
<tr>
<td>Apellido:</td>
<td>
<input type="text" ng-model="form.last_name" />
<span ng-repeat="error in errors.last_name" class="error">{{ error }}</span>
</td>
</tr>
<tr>
<td>Correo electrónico:</td>
<td>
<input type="text" ng-model="form.email" />
<span ng-repeat="error in errors.email" class="error">{{ error }}</span>
</td>
</tr>
<!-- more code -->
</tbody>
</table>
<!-- more code -->
</form>
</div>
Assume the value for ng-submit, ng-repeat exist.
My issue: fields with ng-model are not being populated from $scope.form.
My question: what am I doing wrong? The form works as it should, and the data in the server side is received as it should. My only pain in the *** is that these fields are not being reflected when $rootScope.$apply is called - I need such fields prepopulated from Facebook (I have no issue retrieving such data from Facebook: I can be sure the data arrives if I log it with $window.console.log).
Edit Appendix: API Call
var SDK = function($scope) {
this.$scope = $scope;
this._initialized = false;
this._calls = [];
};
/* ... */
SDK.prototype.api = function(path, method, params, callback) {
var c = this;
this._makeCall(function(){
FB.api(
c.wrap(path),
c.wrap(method),
c.wrap(params),
c.wrap(callback)
);
});
};
/* ... */
SDK.prototype.wrap = function(call) {
var c = this;
return (typeof call !== 'function') ? call : function(){
c.$scope.$apply(call);
};
};
/* ... */
FBModule.factory('AngularFB.SDK', ['$rootScope', sdk]);
I found the error. It was not related to $rootScope.$apply.
A wrapped function with this:
SDK.prototype.wrap = function(call) {
var c = this;
return (typeof call !== 'function') ? call : function(){
c.$scope.$apply(call);
};
};
Did not proxy any parameter. call was passed to $apply, which passes the first parameter being the $rootScope itself. So I had to use "another layer" of closuring, and passing explicitly any param received by the wrapping function:
SDK.prototype.wrap = function(call) {
var c = this;
return (typeof call !== 'function') ? call : function(){
/* copy the params into a new object */
var args = [];
angular.forEach(arguments, function(argument) {
args.push(c.wrap(argument));
});
/* the applied call takes the params and creates a 0-arity function to be applied, which takes the task of calling the target function with the passed params */
c.$scope.$apply(function(){
call.apply(null, args);
});
};
};
So -as I supposed- nothing was wrong with Angular, but took a lot to figure that the passed param was not the expected one.
Solution: proxy the call ensuring it is bridging the passed parameters.
I'm not sure if this will work, but try creating the form object before the api call.
$scope.success = false;
$scope.form = {}; //try creating the object here
SDK.api('/me', function(response) {
$scope.$apply(function () {
//and update it here
$scope.form.first_name = response.first_name;
$scope.form.last_name = response.last_name;
$scope.form.email = response.email;
$scope.formEnabled = true;
$scope.formFetching = false;
});
});

How to remove deleted row in ng-table

I have a grid developed using ng-table and I need to remove selected item from grid table after removing from server-side. Already tried to call the grid loading ajax again, but it's not working.
My Controller,
app.controller('blockController', function($scope, $filter, $q, ngTableParams, $sce, Block) {
// Fetch data from server using RESTful API
$scope.load = function() {
// load serverside data using http resource service
Block.get({}, function (response) { // success
$scope.results = response.data;
var data = response.data; // store result to variable
// Start ng-table with pagination
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10 // count per page
}, {
total: data.length, // length of data
getData: function($defer, params) {
// use build-in angular filter
var orderedData = params.sorting() ? $filter('orderBy')(data, params.orderBy()) : data;
orderedData = params.filter() ? $filter('filter')(orderedData, params.filter()) : orderedData;
params.total(orderedData.length); // set total for recalc pagination
$defer.resolve($scope.blocks = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
// un-check all check boxes
$scope.checkboxes = { 'checked': false, items: {} };
// watch for check all checkbox
$scope.$watch('checkboxes.checked', function(value) {
angular.forEach($scope.blocks, function(item) {
if (angular.isDefined(item.id)) {
$scope.checkboxes.items[item.id] = value;
}
});
});
// watch for data checkboxes
$scope.$watch('checkboxes.items', function(values) {
if (!$scope.blocks) {
return;
}
var checked = 0, unchecked = 0,
total = $scope.blocks.length;
angular.forEach($scope.blocks, function(item) {
checked += ($scope.checkboxes.items[item.id]) || 0;
unchecked += (!$scope.checkboxes.items[item.id]) || 0;
});
if ((unchecked == 0) || (checked == 0)) {
$scope.checkboxes.checked = (checked == total);
}
// grayed checkbox
angular.element(document.getElementById("select_all")).prop("indeterminate", (checked != 0 && unchecked != 0));
}, true);
}, function (error) { // error
$scope.results = [];
// error message display here
});
}
// Call REST API
$scope.load();
/*
|------------------------------
| Delete selected items
|------------------------------
*/
$scope.delete = function() {
var items = [];
// loop through all checkboxes
angular.forEach($scope.blocks, function(item, key) {
if($scope.checkboxes.items[item.id]) {
items.push(item.id); // push checked items to array
}
});
// if at least one item checked
if(items.length > 0) {
// confirm delete
bootbox.confirm("Are you sure to delete this data?", function(result) {
if(result==true) {
for (var i = 0; i < items.length; i++) {
// delete using $http resopurce
Block.delete({id: items[i]}, function (response) { // success
// remove the deleted item from grid here
// show message
}, function (error) { // error
// error message display here
});
}
}
});
}
}; // delete
}); // end controller
HTML Table,
<!-- data table grid -->
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped table-bordered" ng-table="tableParams" show-filter="true">
<tbody>
<tr ng-repeat="block in $data">
<!-- serial number -->
<td data-title="'<?php echo $this->lang->line('sno'); ?>'" style="text-align:center" width="4">{{$index+1}}</td>
<!-- Checkbox -->
<td data-title="''" class="center" header="'ng-table/headers/checkbox.html'" width="4">
<input type="checkbox" ng-model="checkboxes.items[block.id]" />
</td>
<!-- Block Name -->
<td data-title="'<?php echo $this->lang->line('label_cluster_name'); ?>'" sortable="'block_name'" filter="{ 'block_name': 'text' }">
<span ng-if="!block.$edit">{{block.block_name}}</span>
<div ng-if="block.$edit"><input class="form-control" type="text" ng-model="block.block_name" /></div>
</td>
<!-- Description -->
<td data-title="'<?php echo $this->lang->line('label_description'); ?>'" sortable="'description'" >
<span ng-if="!block.$edit">{{block.description}}</span>
<div ng-if="block.$edit"><textarea class="form-control" ng-model="block.description"></textarea></div>
</td>
<!-- Edit / Save button -->
<td data-title="'<?php echo $this->lang->line('label_actions'); ?>'" width="6" style="text-align:center">
<a ng-if="!block.$edit" href="" class="btn btn-inverse btn-sm" ng-click="block.$edit = true"><?php echo $this->lang->line('label_edit'); ?></a>
<a ng-if="block.$edit" href="" class="btn btn-green btn-sm" ng-click="block.$edit = false;update(block)"><?php echo $this->lang->line('label_save'); ?></a>
</td>
</tr>
</tbody>
</table> <!-- table grid -->
You should remove the deleted item from the data collection once the server confirms the deletion.
You can do this manually from within the delete success callback instead of just reloading the complete collection (which is theoretically valid as well but will often be slower).
Then after removing the item from the collection, call the tableParams.reload() method to reload the table so the change is reflected in the table.
You can find a working example of the reload() method right here: http://plnkr.co/edit/QXbrbz?p=info
Hope that helps!
This is working for me:
$scope.deleteEntity = function (entity) {
bootbox.confirm("Are you sure you want to delete this entity ?", function (confirmation) {
if (confirmation) {
$http.delete("/url/" + entity._id)
.success(function (data) {
if (data.status == 1) {
var index = _.indexOf($scope.data, entity);
$scope.data.splice(index, 1);
$scope.tableParams.reload();
} else {
}
});
}
}
);
};
In version 1x of ng-table the following code works. In this example I'm using the MyController as vm approach and user is a $resource class object:
this.removeUser = function (user,i) {
user.$remove().then(function () {
this.tableParams.data.splice(i, 1);
}, function () {
// handle failure
});
});
When called in your HTML table, be sure to pass in $index as the second parameter, eg:
<button class="btn btn-danger btn-xs" ng-click="vm.removeUser(user, $index)" title="Delete User">
<i class="glyphicon glyphicon-trash"></i>
</button>
No need to call tableParams.reload or reload data from the server.

Resources