how to execute the statement after promise is executed? - angularjs

I have used the following directory in my template
i want to change the model value of drop-down to id for it i have used as bellow
<md-select flex class="md-select-form" ng-model="education.degree" placeholder="Degree" save-id id="education.degree_id" ">
<md-option ng-value="degree" ng-repeat="degree in ['High School', 'Associates Degree', 'Bachelor Degree', 'Masters Degree', 'M.B.A', 'M.D', 'Ph.D', 'other']">{{degree}}</md-option>
</md-select>
.directory code
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
console.log("within promise"+scope.id); //it executes second
});
console.log(scope.id); //it executes first
return scope.id;
});
return scope.id;
}
};
})
Once if i try to return the value of ng-model in finally block it also not working
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
// var id = -1;
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
}).finally(function(res){
console.log(scope.id);
return scope.id;
});
});
return scope.id;
}
};
})
i have used Profile.getDegreeList() service to assign the id to relevant drop-down element by using ngModel.$parsers.push(). The problem is in case of service usage before promise going to complete it returns the previous assigned id .
i want to prevent it and need to return the promise id.
how to solve this issue please help?

You can use finally method which will be executed after executing promise.
Profile.getDegreeList()
.success(function(data)
{
angular.forEach(data, function(ob)
{
if (keepGoing)
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
});
console.log("within promise"+scope.id);
})
.finally(function(res)
{
// your code
}
);
Here you go first make a call and then use parsers.
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob) {
if (keepGoing) {
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
ngModel.$parsers.push(function(viewValue) {
var keepGoing = true;
return scope.id ;
});
});
See other links too for more guidance.

Related

Custom directive don't want scope in this how can i achieve?

function contentValidator() {
var _matchContent = {
require: 'ngModel',
scope: {
contentValidator: '='
},
link: contentValidatorFn
};
return _matchContent;
function contentValidatorFn(scope, element, attrs, ctrl) {
scope.$watch(function() {
var combined;
if (scope.contentValidator || ctrl.$viewValue) {
combined = scope.contentValidator + '_' + ctrl.$viewValue;
}
return combined;
}, function(value) {
if (value) {
var origin = scope.contentValidator;
if (origin !== ctrl.$viewValue) {
ctrl.$setValidity("contentValidator", false);
return undefined;
} else {
ctrl.$setValidity("contentValidator", true);
return ctrl.$viewValue;
}
}
});
}
}
I'd suggest you do use $validators pipeline to set validity of field of form.
ngModel.$validators.contentValidator = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return condition ? value : undefined; //condition would be what you wanted to check
};
Basically when you return defined value from $validators contentValidator function, but when you don't return angular will add content-validator class on that fields & the same property gets added to that form field like myForm.formFields.$error.contentValidator = true

angularjs: how to know all directive is ready

I want make sure all directives are ready. How to? Here is what I try, but not correct.
leafUi.factory('leafState', function($rootScope) {
var unreadyUi = [], readyUi = [];
return {
unready: function(ui) {
console.log(ui + ' unready');
unreadyUi.push(ui);
},
ready: function(ui) {
console.warn(ui + ' ready');
readyUi.push(ui);
if (readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
}
}
});
leafUi.directive('leafScroll', function($timeout, leafState, leafScroll) {
return {
restrict: 'E',
transclude: true,
link: function(scope, ele, attrs, ctrl, transclude) {
leafState.unready('leafScroll');
// more code .....
leafState.ready('leafScroll');
}
};
});
leafUi.directive('leafContent', function($timeout, leafState, leafScroll) {
return {
restrict: 'E',
transclude: true,
link: function(scope, ele, attrs, ctrl, transclude) {
leafState.unready('leafContent');
// more code .....
leafState.ready('leafContent');
}
};
});
// more directive.....
Here is an example log:
From the log, we can know that directive ready and unready can be separated by other directive and length of readyUi has many chance to equal to unreadyUi.
So how can I assure all directives are ready?
Perhaps add another condition to what you define as leafUiReady could help you. Since the directives are loaded almost synchronously you get the logic that your undreadyUi and readyUi are almost always the same length during compilation.
Perhaps add logic like this to your leafState factory where you also look at the DOM ready-function.
leafUi.factory('leafState', function($rootScope) {
var unreadyUi = [], readyUi = [];
var domReady = false;
angular.element(document).ready(function () {
domReady = true;
if (readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
});
return {
unready: function(ui) {
console.log(ui + ' unready');
unreadyUi.push(ui);
},
ready: function(ui) {
console.warn(ui + ' ready');
readyUi.push(ui);
if (domReady && readyUi.length === unreadyUi.length) {
$rootScope.$broadcast('leafUiReady', 'all leaf ui component is ready');
// unreadyUi = null;
// readyUi = null;
}
}
}
});
Perhaps change some other logic to make this code a bit cleaner.

Angular.js: how to test both directive parser and formatter?

I have implemented a simple directive to check username (it should match a regexp, and some more constraint; on blur it's capitalized).
This is my current implementation:
app.directive('checkUserName', function() {
return {
require: 'ngModel',
scope: {
value: '=ngModel'
},
link: function(scope, elm, attrs, model) {
var USERNAME_REGEXP = /^[^_.$\[\]#\/][^.$\[\]#\/]*$/;
model.$parsers.unshift(function(viewValue) {
var user;
var retval;
if (!viewValue) {
model.$setValidity('required', false);
retval = null;
}
if (USERNAME_REGEXP.test(viewValue)) {
if (!user) {
model.$setValidity('taken', true);
model.$setValidity('invalid', true);
retval = viewValue;
} else {
model.$setValidity('taken', false);
model.$setValidity('invalid', true);
}
} else {
model.$setValidity('taken', true);
model.$setValidity('invalid', viewValue === '');
retval = viewValue;
}
return retval;
});
model.$formatters.push(function(modelValue) {
if (modelValue) {
modelValue = capitalize(modelValue);
}
return modelValue;
});
}
};
});
app.directive('renderOn', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
var event = attrs.renderOn;
elm.bind(event, function() {
var viewValue = ctrl.$modelValue;
for (var i in ctrl.$formatters) {
viewValue = ctrl.$formatters[i](viewValue);
}
ctrl.$viewValue = viewValue;
ctrl.$render();
});
}
};
});
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
So the question is:
How do I test it?
I did set up a fiddle with the karma test I did write, but the blur handling is obviously wrong...
A secondary question is: is this 'split' implementation (checkUserName + renderOn) advisable strategy when implementing both 'parsing' and 'formatting' directives?

AngularJS $watch newValue is undefined

I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch

How to create a AngularJS jQueryUI Autocomplete directive

I am trying to create a custom directive that uses jQueryUI's autocomplete widget. I want this to be as declarative as possible. This is the desired markup:
<div>
<autocomplete ng-model="employeeId" url="/api/EmployeeFinder" label="{{firstName}} {{surname}}" value="id" />
</div>
So, in the example above, I want the directive to do an AJAX call to the url specified, and when the data is returned, show the value calculated from the expression(s) from the result in the textbox and set the id property to the employeeId. This is my attempt at the directive.
app.directive('autocomplete', function ($http) {
return {
restrict: 'E',
replace: true,
template: '<input type="text" />',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
elem.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.then(function (data) {
response($.map(data, function (item) {
var result = {};
result.label = item[attrs.label];
result.value = item[attrs.value];
return result;
}))
});
},
select: function (event, ui) {
ctrl.$setViewValue(elem.val(ui.item.label));
return false;
}
});
}
}
});
So, I have two issues - how to evaluate the expressions in the label attribute and how to set the property from the value attribute to the ngModel on my scope.
Here's my updated directive
(function () {
'use strict';
angular
.module('app')
.directive('myAutocomplete', myAutocomplete);
myAutocomplete.$inject = ['$http', '$interpolate', '$parse'];
function myAutocomplete($http, $interpolate, $parse) {
// Usage:
// For a simple array of items
// <input type="text" class="form-control" my-autocomplete url="/some/url" ng-model="criteria.employeeNumber" />
// For a simple array of items, with option to allow custom entries
// <input type="text" class="form-control" my-autocomplete url="/some/url" allow-custom-entry="true" ng-model="criteria.employeeNumber" />
// For an array of objects, the label attribute accepts an expression. NgModel is set to the selected object.
// <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" ng-model="criteria.employeeNumber" />
// Setting the value attribute will set the value of NgModel to be the property of the selected object.
// <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" value="id" ng-model="criteria.employeeNumber" />
var directive = {
restrict: 'A',
require: 'ngModel',
compile: compile
};
return directive;
function compile(elem, attrs) {
var modelAccessor = $parse(attrs.ngModel),
labelExpression = attrs.label;
return function (scope, element, attrs) {
var
mappedItems = null,
allowCustomEntry = attrs.allowCustomEntry || false;
element.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.success(function (data) {
mappedItems = $.map(data, function (item) {
var result = {};
if (typeof item === 'string') {
result.label = item;
result.value = item;
return result;
}
result.label = $interpolate(labelExpression)(item);
if (attrs.value) {
result.value = item[attrs.value];
}
else {
result.value = item;
}
return result;
});
return response(mappedItems);
});
},
select: function (event, ui) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, ui.item.value);
});
if (attrs.onSelect) {
scope.$apply(attrs.onSelect);
}
element.val(ui.item.label);
event.preventDefault();
},
change: function () {
var
currentValue = element.val(),
matchingItem = null;
if (allowCustomEntry) {
return;
}
if (mappedItems) {
for (var i = 0; i < mappedItems.length; i++) {
if (mappedItems[i].label === currentValue) {
matchingItem = mappedItems[i].label;
break;
}
}
}
if (!matchingItem) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, null);
});
}
}
});
};
}
}
})();
Sorry to wake this up... It's a nice solution, but it does not support ng-repeat...
I'm currently debugging it, but I'm not experienced enough with Angular yet :)
EDIT:
Found the problem. elem.autocomplete pointed to elem parameter being sent into compile function. IT needed to point to the element parameter in the returning linking function. This is due to the cloning of elements done by ng-repeat. Here is the corrected code:
app.directive('autocomplete', function ($http, $interpolate, $parse) {
return {
restrict: 'E',
replace: true,
template: '<input type="text" />',
require: 'ngModel',
compile: function (elem, attrs) {
var modelAccessor = $parse(attrs.ngModel),
labelExpression = attrs.label;
return function (scope, element, attrs, controller) {
var
mappedItems = null,
allowCustomEntry = attrs.allowCustomEntry || false;
element.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.success(function (data) {
mappedItems = $.map(data, function (item) {
var result = {};
if (typeof item === "string") {
result.label = item;
result.value = item;
return result;
}
result.label = $interpolate(labelExpression)(item);
if (attrs.value) {
result.value = item[attrs.value];
}
else {
result.value = item;
}
return result;
});
return response(mappedItems);
});
},
select: function (event, ui) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, ui.item.value);
});
elem.val(ui.item.label);
event.preventDefault();
},
change: function (event, ui) {
var
currentValue = elem.val(),
matchingItem = null;
if (allowCustomEntry) {
return;
}
for (var i = 0; i < mappedItems.length; i++) {
if (mappedItems[i].label === currentValue) {
matchingItem = mappedItems[i].label;
break;
}
}
if (!matchingItem) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, null);
});
}
}
});
}
}
}
});

Resources