Related
I'm using a directive to interact with a service and am encountering some trouble with the view showing the latest data.
I setup the following example. You can see that when a controller interacts with the Service, the view will update with the latest data. If you click the directive link, you can see in the console the data was changed but the view is not updated with that data.
http://jsfiddle.net/kisonay/pv8towqc/
What am I missing?
JavaScript:
var app = angular.module('myApp', []);
app.factory('Service', function() {
var Service = {};
Service.data = {};
Service.data.active = false;
Service.data.one = {};
Service.data.many = [];
Service.changeActive = function(state) {
state = state || false;
Service.data.active = state;
};
Service.setOne = function(one) {
Service.data.one = one;
};
Service.setMany = function(many) {
Service.data.many = many;
};
return Service;
});
app.directive('launcher', function(Service) {
return {
restrict: "A",
link: function(scope, elem, attrs) {
elem.bind('click', function(event) {
if (Service.data.active) {
Service.changeActive(false);
} else {
Service.changeActive(true);
}
console.log(Service.data); // shows data changed
});
}
};
});
function Ctrl1($scope, Service) {
$scope.ServiceData = Service.data;
}
function Ctrl2($scope, Service) {
$scope.active = function() {
Service.changeActive(true);
};
$scope.inactive = function() {
Service.changeActive(false);
};
}
HTML
<div ng-controller="Ctrl1">
{{ServiceData}}
</div>
<hr />
<div ng-controller="Ctrl2">
Directive: <a href="#" launcher>Change</a>
<hr /> Controller:
<button ng-click="active()">
Active
</button>
<button ng-click="inactive()">
Inactive
</button>
</div>
Your event listener executes but Angular doesn't know anything about it, so it doesn't know it has to detect changes.
Add scope.$apply(); at the evn of the click listener, and it will work as expected.
app.directive('launcher', function(Service) {
return {
restrict: "A",
link: function(scope, elem, attrs) {
elem.bind('click', function(event) {
scope.$apply(function() {
if (Service.data.active) {
Service.changeActive(false);
} else {
Service.changeActive(true);
}
});
});
}
};
});
I’ve been playing around with Upload file - Streaming method. The original code, here:
https://github.com/aspnet/Docs/tree/master/aspnetcore/mvc/models/file-uploads/sample/FileUploadSample
However, I’m trying to get the data in the value attribute of <input value=” ”> using Angular, the idea is that I can POST the value into my MVC model instead of whatever is typed by the user (as in the original code). So, I have done this change to the input value property.
Streaming/Index.cshtml:
<div ng-app="myApp">
<div ng-controller="myCtrl">
..
<input value="#Model.name” type="text" name="Name" ng-model="name"/>
..
<button ng-click="createUser()">Create User</button>
..
</div>
</div>
#section scripts{
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="~/js/app.js"></script>
}
However, with Angular code running under app.js, the following piece of code actually fails with status code 400. This is because the passed value is “” and not the data under of value attribute of the HTML input tag.
App.js:
var User = (function () {
function User(name) {
this.name = name;
}
return User;
}());
var myApp = angular.module('myApp', []);
myApp.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function () {
scope.$apply(function () {
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
myApp.service('userService', ['$http', function ($http) {
this.createUser = function(user) {
var fd = new FormData();
fd.append('name', user.name);
return $http.post('/streaming/upload', fd, {
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
});
};
}]);
myApp.controller('myCtrl', ['$scope', 'userService', function ($scope, userService) {
$scope.createUser = function () {
$scope.showUploadStatus = false;
$scope.showUploadedData = false;
var user = new User($scope.name);
userService.createUser(user).then(function (response) { // success
if (response.status == 200) {
$scope.uploadStatus = "User created sucessfully.";
$scope.uploadedData = response.data;
$scope.showUploadStatus = true;
$scope.showUploadedData = true;
$scope.errors = [];
}
},
function (response) { // failure
$scope.uploadStatus = "User creation failed with status code: " + response.status;
$scope.showUploadStatus = true;
$scope.showUploadedData = false;
$scope.errors = [];
$scope.errors = parseErrors(response);
});
};
}]);
function parseErrors(response) {
var errors = [];
for (var key in response.data) {
for (var i = 0; i < response.data[key].length; i++) {
errors.push(key + ': ' + response.data[key][i]);
}
}
return errors;
}
The solution must be a simple one, but after much research, I haven’t been able to find out how to modify it to make the data in the value=’’” attribute being passed across. This might be a stupid question but a headache for me however since I’m a total newbie regarding Angular. Please have some mercy, help.
Thanks
Use the ng-init directive to initialize the model:
<input ng-init="name= #Model.name" type="text" name="Name" ng-model="name"/>
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.
It's so simple in a non-Angular environment. Just html and a two line of js code to show a modal confirmation dialog on the screen.
Now I am developting an AngularJS project in which I am using ui-bootstrap modal confirmation dialogs all over the place and I am sick of creating new controllers even for simple things like "Are you sure to delete this record?" kind of stuff.
How do you handle these simple situations? I am sure some people wrote some directives to simplify the needs.
I am asking you to share your experiences or the projects you know about that subject.
so create a reusable service for that... read here
code here:
angular.module('yourModuleName').service('modalService', ['$modal',
// NB: For Angular-bootstrap 0.14.0 or later, use $uibModal above instead of $modal
function ($modal) {
var modalDefaults = {
backdrop: true,
keyboard: true,
modalFade: true,
templateUrl: '/app/partials/modal.html'
};
var modalOptions = {
closeButtonText: 'Close',
actionButtonText: 'OK',
headerText: 'Proceed?',
bodyText: 'Perform this action?'
};
this.showModal = function (customModalDefaults, customModalOptions) {
if (!customModalDefaults) customModalDefaults = {};
customModalDefaults.backdrop = 'static';
return this.show(customModalDefaults, customModalOptions);
};
this.show = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
if (!tempModalDefaults.controller) {
tempModalDefaults.controller = function ($scope, $modalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function (result) {
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
$modalInstance.dismiss('cancel');
};
};
}
return $modal.open(tempModalDefaults).result;
};
}]);
html for display
<div class="modal-header">
<h3>{{modalOptions.headerText}}</h3>
</div>
<div class="modal-body">
<p>{{modalOptions.bodyText}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn"
data-ng-click="modalOptions.close()">{{modalOptions.closeButtonText}}</button>
<button class="btn btn-primary"
data-ng-click="modalOptions.ok();">{{modalOptions.actionButtonText}}</button>
</div>
once this is done... you just have to inject above service whereever you want to create a dialog box, example below
$scope.deleteCustomer = function () {
var custName = $scope.customer.firstName + ' ' + $scope.customer.lastName;
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Delete Customer',
headerText: 'Delete ' + custName + '?',
bodyText: 'Are you sure you want to delete this customer?'
};
modalService.showModal({}, modalOptions)
.then(function (result) {
//your-custom-logic
});
}
You can see my example. whatever i'v done.
<div ng-app="myApp" ng-controller="firstCtrl">
<button ng-click="delete(1);">Delete </button>
</div>
script
var app = angular.module("myApp", []);
app.controller('firstCtrl', ['$scope','$window', function($scope,$window) {
$scope.delete = function(id) {
deleteUser = $window.confirm('Are you sure you want to delete the Ad?');
if(deleteUser){
//Your action will goes here
alert('Yes i want to delete');
}
};
}])
You can use the Angular Confirm library.
When included, it's became available as a directive:
<button type="button" ng-click="delete()" confirm="Are you sure?">Delete</button>
As well as a service:
angular.module('MyApp')
.controller('MyController', function($scope, $confirm) {
$scope.delete = function() {
$confirm({text: 'Are you sure you want to delete?', title: 'Delete it', ok: 'Yes', cancel: 'No'})
.then(function() {
// send delete request...
});
};
});
For anything that has code that is triggered with a ng-click I just add a confirm attribute
eg
<a confirm="Are you sure?" ng-click="..."></a>
and confirm comes from (not mine, found on the web)
app.controller('ConfirmModalController', function($scope, $modalInstance, data) {
$scope.data = angular.copy(data);
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}).value('$confirmModalDefaults', {
template: '<div class="modal-header"><h3 class="modal-title">Confirm</h3></div><div class="modal-body">{{data.text}}</div><div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>',
controller: 'ConfirmModalController'
}).factory('$confirm', function($modal, $confirmModalDefaults) {
return function(data, settings) {
settings = angular.extend($confirmModalDefaults, (settings || {}));
data = data || {};
if ('templateUrl' in settings && 'template' in settings) {
delete settings.template;
}
settings.resolve = { data: function() { return data; } };
return $modal.open(settings).result;
};
})
.directive('confirm', function($confirm) {
return {
priority: 1,
restrict: 'A',
scope: {
confirmIf: "=",
ngClick: '&',
confirm: '#'
},
link: function(scope, element, attrs) {
function reBind(func) {
element.unbind("click").bind("click", function() {
func();
});
}
function bindConfirm() {
$confirm({ text: scope.confirm }).then(scope.ngClick);
}
if ('confirmIf' in attrs) {
scope.$watch('confirmIf', function(newVal) {
if (newVal) {
reBind(bindConfirm);
} else {
reBind(function() {
scope.$apply(scope.ngClick);
});
}
});
} else {
reBind(bindConfirm);
}
}
}
})
My google FOO has failed me and I cannot find the source site for this. I will update if I find it.
You can create a simple factory like this
angular.module('app')
.factory('modalService', [
'$modal', function ($modal) {
var self = this;
var modalInstance = null;
self.open = function (scope, path) {
modalInstance = $modal.open({
templateUrl: path,
scope: scope
});
};
self.close = function () {
modalInstance.dismiss('close');
};
return self;
}
]);
In your controller
angular.module('app').controller('yourController',
['$scope','modalService',function($scope,modalService){
$scope.openModal=function(){
modalService.open($scope,'modal template path goes here');
};
$scope.closeModal=function(){
modalService.close();
//do something on modal close
};
}]);
I have passed $scope in service function so that you can access closeModal function and in case you want to access some data from your controller .
In your html
<button ng-click="openModal()">Open Modal</button>
I have an async validator that calls a service will return 200 (OK) or 400 (BadRequest) with a message.
I'd like to return the message as the validation message but can't figure out how to get the message to show up. I've tried a few things without success.
<div ng-messages="searchFilterForm.search.$error">
<small ng-message="valid">Invalid search filter. Reason: {{MY_BAD_REQUEST_RESPONSE_MESSAGE_GOES_HERE}}</small>
</div>
Here's a jsfiddle: http://jsfiddle.net/javajunkie314/r6oyLe26/3/
The idea is that the myValidator and myErrorMessage directives synchronize their error message via the myErrorHandler directive. Annoyingly, I can't find any way to access the reason passed to reject by the validator.
Edit: I've updated the fiddle. Now myValidator uses two asynchronous validators. I've also created a ValidatorPromise to handle updating the myErrorHandler controller. This is as I described in my comment below.
The HTML:
<form ng-controller="testCtrl">
<div my-error-handler="">
<input type="text" name="foo" ng-model="foo.text" my-validator="">
<div my-error-message=""></div>
</div>
</form>
The JavaScript:
(function () {
var app = angular.module('myApp', []);
/* Wraps a promise. Used to update the errorHandler controller when the
* validator resolves. */
app.factory('ValidatorPromise', function ($q) {
return function (errorHandler, name, promise) {
return promise.then(
// Success
function (value) {
// No error message for success.
delete errorHandler.error[name];
return value;
},
// Failure
function (value) {
// Set the error message for failure.
errorHandler.error[name] = value;
return $q.reject(value);
}
);
};
});
app.controller('testCtrl', function ($scope) {
$scope.foo = {
text: ''
};
});
app.directive('myErrorHandler', function () {
return {
controller: function () {
this.error = {};
}
};
});
app.directive('myValidator', function ($timeout, $q, ValidatorPromise) {
return {
require: ['ngModel', '^myErrorHandler'],
link: function (scope, element, attrs, controllers) {
var ngModel = controllers[0];
var myErrorHandler = controllers[1];
ngModel.$asyncValidators.test1 = function () {
return ValidatorPromise(
myErrorHandler, 'test1',
$timeout(function () {}, 1000).then(function () {
return $q.reject('Fail 1!');
})
);
};
ngModel.$asyncValidators.test2 = function () {
return ValidatorPromise(
myErrorHandler, 'test2',
$timeout(function () {}, 2000).then(function () {
return $q.reject('Fail 2!');
})
);
};
}
};
});
app.directive('myErrorMessage', function () {
return {
require: '^myErrorHandler',
link: function (scope, element, attrs, myErrorHandler) {
scope.error = myErrorHandler.error;
},
/* This template could use ngMessages to display the errors
* nicely. */
template: 'Error: {{error}}'
};
});
})();
I prepared a plunker that I think do what you want. It uses a modified version of ui-validate but I think you can get the idea anyway.
I defined service called 'remoteValidator'. It has a method 'validate' that simulates the trip to the server returning a promise that rejects with a message if the value is 'bad'.
app.service("remoteValidator", function($q, $timeout) {
this.validate = function(value) {
return $q(function(resolve, reject) {
setTimeout(function() {
if (value === "bad") {
reject("Value is bad");
} else {
resolve();
}
}, 1000);
});
}
});
Then I defined in the controller a validator that use that service and capture the error message and return the promise in order to your validator of choice do the work.
$scope.remoteValAsync = function(value) {
var promise = remoteValidator.validate(value);
promise.then(function() {
delete $scope.errorMsg;
}, function(error) {
$scope.errorMsg = error;
});
return promise;
};
This way, when you write 'bad' in the async inputbox, when promises resolve, error message appears in the scope along with the validators name inside form.field.$error list.
A snippet of the html:
<div class="input-group-inline">
<input class="form-control" type="text" name="nextPassword" ng-model="passwordForm.nextPassword"
ui-validate-async="{ badValue: 'remoteValAsync($value)' }" />
<p>Pending validations:</p>
<ul>
<li ng-repeat="(key, errors) in myForm.nextPassword.$pending track by $index">{{ key }}</li>
</ul>
<p>Bad validations:</p>
<ul>
<li ng-repeat="(key, errors) in myForm.nextPassword.$error track by $index">{{ key }}</li>
</ul>
<p>{{ errorMsg }}</p>
</div>
I know droping the message in 'errorMsg' is not the best. It could be made better but I have to get in into de ui-validate-async directive.
I hope it helps.
I would suggest do the global exception handling on server side
if error occurs then send success as false with error message
json.put("success", false);
json.put("message",errorMessage);
Otherwise send json with success as true with value
json.put("success", true);
json.put("values",output);
Then on UI side handle exception using
$.post(serverUrl,valuesJson)
.success(function(responseData) {
var responseData = JSON.parse(responseData);
if(responseData.success){
\\ write your logic
} else {
alert(responseData.message);
}
}).fail(function(xhr, textStatus, errorThrown) {
//check for session out
if(xhr.status == 403)
alert( "Your session is timed out.redirected to Login screen." );
// redirect to login
})
.error(function(responseData){
console.log(responseData);
});