How to set ng-model variable from custom validation directive - angularjs

How to call controller scope from angularjs directive outside link function
$scope is unknown provider here:
app.directive('checkId', function($parse,$scope, $http){//here $scope is unknown provider
return{
restrict:'A',
link: function(scope, element, attrs){
element.bind('change', function(){
var filteredLength = $parse(attrs.filtered)(scope);
console.log(filteredLength);
var newFilteredArray = scope.stateList.filter(function(values){
return values.toUpperCase() == scope.TRDetail.memberNo.toUpperCase();
});
if(filteredLength == 0){
console.log("if called");
scope.TRDetail.memberNo = "";
}else if(newFilteredArray.length == 0){
scope.TRDetail.memberNo = "";
};
scope.$apply();
thisIsCalled(scope.TRDetail.memberNo);
});
}
};
function thisIsCalled(someData){
if(someData == ""){
alert("NOT doing some $http srvices");
}else{
$http.post("SomeURL", someData).then(function(response, error){
if(error){
alert("sorry no data found" + error.message);
}else{
$scope.TRDetail.memberName = response.data;
}
});
};
};
});
HTML:
<div class="form-group col-md-3">
<label>MemberShip NO:<span ng-if="(stateList|filter:TRDetail.memberNo).length==0" style="color:red;">(nothing found)</span></label>
<input list="idList" class="form-control" name="browser"
ng-model="TRDetail.memberNo"
filtered="(stateList|filter:TRDetail.memberNo).length"
check-id>
<datalist id="idList">
<option ng-repeat="x in stateList">{{x}}</option>
</datalist>
</div>
<div class="form-group col-md-9">
<label>Member Name</label>
<input type="text" ng-disabled="true"
ng-model="TRDetail.memberName" class="form-control"
placeholder="type name here..">
</div>
I am unable to update TRDetail.memberName outside from link function.
any help, how to do?

tl; dr;
Use ngModel.$setViewValue
When a custom directive is combined with ng-model directive, the directive should use the ngModelController API:
app.directive('checkId', function($parse,$http){
return{
require: 'ngModel',
restrict:'A',
link: function(scope, element, attrs, ngModel){
̶e̶l̶e̶m̶e̶n̶t̶.̶b̶i̶n̶d̶(̶'̶c̶h̶a̶n̶g̶e̶'̶,̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶(̶)̶{̶
ngModel.$viewChangeListeners.push(onChange);
function onChange(){
if (!ngModel.$modelValue) return;
var filteredLength = $parse(attrs.filtered)(scope);
console.log(filteredLength);
var newFilteredArray = scope.stateList.filter(function(values){
̶r̶e̶t̶u̶r̶n̶ ̶v̶a̶l̶u̶e̶s̶.̶t̶o̶U̶p̶p̶e̶r̶C̶a̶s̶e̶(̶)̶ ̶=̶=̶ ̶s̶c̶o̶p̶e̶.̶T̶R̶D̶e̶t̶a̶i̶l̶.̶m̶e̶m̶b̶e̶r̶N̶o̶.̶t̶o̶U̶p̶p̶e̶r̶C̶a̶s̶e̶(̶)̶;̶
return values.toUpperCase() == ngModel.$modelValue.toUpperCase();
});
if(filteredLength == 0){
console.log("if called");
̶s̶c̶o̶p̶e̶.̶T̶R̶D̶e̶t̶a̶i̶l̶.̶m̶e̶m̶b̶e̶r̶N̶o̶ ̶=̶ ̶"̶"̶;̶
ngModel.$setViewValue("");
}else if(newFilteredArray.length == 0){
̶s̶c̶o̶p̶e̶.̶T̶R̶D̶e̶t̶a̶i̶l̶.̶m̶e̶m̶b̶e̶r̶N̶o̶ ̶=̶ ̶"̶"̶;̶
ngModel.$setViewValue("");
};
̶s̶c̶o̶p̶e̶.̶$̶a̶p̶p̶l̶y̶(̶)̶;̶
̶t̶h̶i̶s̶I̶s̶C̶a̶l̶l̶e̶d̶(̶s̶c̶o̶p̶e̶.̶T̶R̶D̶e̶t̶a̶i̶l̶.̶m̶e̶m̶b̶e̶r̶N̶o̶)̶;̶
thisIsCalled(ngModel.$modelValue);
}
function thisIsCalled(someData){
if(someData == ""){
alert("NOT doing some $http srvices");
}else{
$http.post("SomeURL", someData)
.then(function(response){
ngModel.$setViewValue(response.data);
})
.catch(function(error) {
alert("sorry no data found" + error.status);
});
};
}
}
};
For more information, see
AngularJS ngModelController API Reference

Your function thisIsCalled(someData) is just not living inside your angular app. You should get access to the $scope element through the directive's controller:
angular.directive('checkId', function($parse, $scope, $http) {
return {
restrict: 'A',
link: //your link stuff
controller: myController
};
});
function myController($scope) {
//here you can access the scope
}

Related

Angular 1.x - retrieve directive value from controller and assign it back to parent scope

I'm unable to get a variable value from directive to use that back in a controller. I do not have to bind the value to a view. All I need is to set 'cleanInputValue' from directive to $scope.keywords in Controller.
Here is my directive and controller -
Html with md-autocomplete for keywords field - search box.
<form id="searchbox" ng-controller="navsearchController as sc" title="Search this site" layout="row">
<md-autocomplete
md-no-cache="true"
md-selected-item="sc.currentKeyword"
md-search-text="keywords"
md-items="item in querySearch(keywords)"
md-item-text="item.display"
md-min-length="3"
md-input-name="search"
md-input-id="search-nav"
md-clear-button="false"
placeholder="Search"
md-dropdown-position="bottom"
flex>
<md-item-template>
<span md-highlight-text="keywords" md-highlight-flags="gi">{{item.display}}</span>
</md-item-template>
</md-autocomplete>
<div class="search-button" flex="none">
<button type="submit" ng-click="sc.search()" title="Search" tabindex="0">GO</button>
</div>
</form>
Directive:
.directive('test', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
text: '#text'
},
link:function(scope, element, attrs, modelCtrl){
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue === undefined){
return '';
}
var cleanInputValue = inputValue.replace(/[^\w\s\-\"]/gi, '');
if (cleanInputValue != inputValue) {
modelCtrl.$setViewValue(cleanInputValue);
modelCtrl.$render();
}
return cleanInputValue;
});
//console.log(scope.text);
}
};
})
Controller:
.controller('navsearchController', function($timeout, $element, $compile, $scope, $rootScope, $http, $location, DataService, $routeParams, $filter, $route){
var _this = this;
$timeout(function () {
var myAutoCompleteInput =
angular.element($element[0].querySelector('#search-nav'));
myAutoCompleteInput.attr("test", "test");
//myAutoCompleteInput.attr("text", "blah");
console.log($scope.keywords);
$compile(myAutoCompleteInput)($scope);
});
_this.search = function(){
xyzStorage.set('currentKeyword', $scope.keywords);
$scope.keywords = $filter('removeSpecialChars')($scope.keywords);
$location.path('/xyz/search/' + $scope.keywords);
$location.url($location.path());
$location.search({
range: xyzStorage.get('itemPerPage'),
})
$route.reload();
};
})
What you really want to do is bind the value from your controller to your directive. Don't think of it as "returning" a value from your directive.
Let's take a look.
.directive('test', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
text: '#text',
cleanInputValue: '=testTextClean' // Adding a new TWO WAY binding here!
},
link:function(scope, element, attrs, modelCtrl){
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue === undefined){
return; // exit the function and don't assign, ok
}
// Now we use scope
scope.cleanInputValue = inputValue.replace(/[^\w\s\-\"]/gi, '');
if (scope.cleanInputValue != inputValue) {
modelCtrl.$setViewValue(scope.cleanInputValue);
modelCtrl.$render();
}
// no longer need a return
});
}
};
})
In your controller you are accessing the input element within the md-autocomplete component
.controller('navsearchController', function($timeout, $element, $compile, $scope, $rootScope, $http, $location, DataService, $routeParams, $filter, $route){
var _this = this;
$timeout(function () {
var myAutoCompleteInput =
angular.element($element[0].querySelector('#search-nav'));
myAutoCompleteInput.attr("test", "test");
myAutoCompleteInput.attr("test-text-clean", "sc.keywords");
console.log($scope.keywords);
$compile(myAutoCompleteInput)($scope);
});
_this.search = function(){
xyzStorage.set('currentKeyword', $scope.keywords);
$scope.keywords = $filter('removeSpecialChars')($scope.keywords);
$location.path('/xyz/search/' + $scope.keywords);
$location.url($location.path());
$location.search({
range: xyzStorage.get('itemPerPage'),
})
$route.reload();
};
})
Now in your controller the value in $scope.keywords will always have the updated value set from your directive.

Controller Function Not called inside Directive

I have a controller function and I cannot call it inside the directive. I am trying hard. is there any thing Else i am failing to do? please tell me. I have included my code here. I have searched many places followed many answers and now I am stuck at this
(function () {
var app = angular.module("featureModule", ['ngRoute']);
//
app.directive('myGoogleAutocomplete', function () {
return {
replace: true,
require: 'ngModel',
scope: {
ngModel: '=',
googleModel: '=',
onSelect: '&?', // optional callback on selected successfully: 'onPostedBid(googleModel)'
},
template: '<input class="form-control" type="text" style="z-index: 100000;" autocomplete="on">',
link: function ($scope, element, attrs, model)
{
var autocomplete = new google.maps.places.Autocomplete(element[0], googleOptions);
google.maps.event.addListener(autocomplete, 'place_changed', function () {
$scope.$apply(function () {
var place = autocomplete.getPlace();
if (!place.geometry)
{
// User entered the name of a Place that was not suggested and pressed the Enter key, or the Place Details request failed.
model.$setValidity('place', false);
//console.log("No details available for input: '" + place.name + "'");
return;
}
$scope.googleModel = {};
$scope.googleModel.placeId = place.place_id;
$scope.googleModel.latitude = place.geometry.location.lat();
$scope.googleModel.longitude = place.geometry.location.lng();
$scope.googleModel.formattedAddress = place.formatted_address;
if (place.address_components) {
$scope.googleModel.address = [
$scope.extract(place.address_components, 'route'),
$scope.extract(place.address_components, 'street_number')
].join(' ');
}
model.$setViewValue(element.val());
model.$setValidity('place', true);
if (attrs.onSelect)
{
//how to call controller function here?
$scope.onSelect({ $item: $scope.googleModel });
}
});
});
}
}
});
app.controller("featureController", function($scope,$http,$rootScope,close,ModalService,NgMap) {
console.log($rootScope.permService);
$scope.onSelect=function(val)
{
console.log(val);
}
});
<my-google-autocomplete id="address" name="address" ng-model="task.house_no" google-model="model.googleAddress"
on-select="vm.onSelectGoogleAddress($item)" autocomplete="off" required>
</my-google-autocomplete>
There is no onSelectGoogleAddress() function in the controller. I see only onSelect() function. Change on-select value passed in the html.
<my-google-autocomplete id="address" name="address" ng-model="task.house_no" google-model="model.googleAddress"
on-select="onSelect($item)" autocomplete="off" required>
</my-google-autocomplete>
You can bind callback event by using elemet inside link function.
Here is the example . Hope it helps.
Let the controller having callback function from directive as below
app.controller('MainCtrl', function($scope) {
$scope.SayHello=function(id){
alert(id);
}
});
Let the directive
app.directive('dirDemo', function () {
return {
scope: {
okCallback: '&'
},
template: '<input type="button" value="Click me" >',
link: function (scope, element, attrs) {
var param='from directive';
element.bind('click', function () {
scope.$apply(function () {
scope.okCallback({id:param});
});
});
}
}
});
HTML is
<body ng-controller="MainCtrl">
<div dir-demo
ok-callback="SayHello(id)"
</div>
</body>
Working plunker https://plnkr.co/edit/RbFjFfqR1MDDa3Jwe8Gq?p=preview

Using $resource with $asyncValidators in angularjs

This is what I wrote but angular keeps complaining about the $asyncValidators telling me is {}. I do not want to work with $http nor with $q. This is the faulty code I wrote:
.directive('checkEmail', ['toolBox', function(toolBox){
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel){
ngModel.$asyncValidators.emailExists = function(userEmail){
var promise = toolBox.checkEmailExist(userEmail);
return promise.get(function success(res){
return true;
}, function error(res){
return false;
});
};
}
};
}])
Has any of you worked validation with $resource? Where I get it wrong? toolBox.checkEmailExist(userEmail) comes from a service that looks like this
angular.module('toolBoxService', ['ngResource'])
.factory('toolBox', ['$resource','$log', function($resource, $log){
var dataObj = {};
dataObj.checkEmailExist = function(email){
return $resource('api/users/email', {email: email});
};
return dataObj;
}]);
and the form element looks like
<!-- Email field -->
<div class="input-group">
<span class="input-group-addon" id="userEmail">*</span>
<label class="control-label" for="userEmailField"></label>
<input class="form-control"
name="userEmail"
id="userEmailField"
required=""
placeholder="Email"
type="email"
ng-model="data.email"
ng-model-options="{ debounce: { default : 300, blur: 0 }}"
check-email>
</input>
</div>
<!-- Validation of the email -->
<div class="help-block form-error-messages"
ng-messages="registerForm.userEmail.$error"
ng-show="registerForm.userEmail.$touched"
role="alert"
ng-messages-multiple>
<div ng-messages-include="FACETS/errors/errorMessages.html"></div>
</div>
The only solution I've came up with is this
// checking for email in the database
.directive('checkEmail', ['toolBox', function(toolBox) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$validators.checkEmail = function(modelValue, viewValue){
var currentVal = modelValue || viewValue;
toolBox.checkEmailExist(currentVal)
.get(function success(resp){
if(resp.email == currentVal) {
ngModel.$setValidity('checkEmail', false);
} else {
ngModel.$setValidity('checkEmail', true);
}
});
};
} // end link
}; // end return
}])
$resource is not working with the $asyncValidators unfortunately. Took me 2 days to realise this. I hope there was an answer, a more expert voice on the matter.
It might be that you have to return the $promise attached to the resource instance object, rather than the object itself.
If your api returns an OK 200 response when the username does exist and an error status (e.g. 404 Not Found) when it does not, then the following approach works:
.directive('usernameExists', ['$resource', function ($resource) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ngModel) {
ngModel.$asyncValidators.usernameExists = function (userName) {
return $resource('/api/Account/:userName')
.get({ userName: userName }).$promise;
};
}
}
}])
I can solve it, it was matter of playing a bit more with promises
.directive('validateNbr',['$q','PreavisoService', function($q, PreavisoService) {
return {
require: 'ngModel',
restrict: '',
link: function(scope, elm, attrs, ngModel) {
ngModel.$asyncValidators.validateNbr = function(modelValue){
var def = $q.defer();
PreavisoService.checkUnitDigit({nbr:ngModel.$viewValue}).$promise.then(
function(result){
if(result.isValid === false){
def.reject();
}else{
def.resolve();
}
});
return def.promise;
};
}
};
}]);
hope it helps

How to access ng-model value in directive?

I created a directive for google map auto-complete. everything is working fine, but the problem is when I need to access the value of input and re-set it. it doesn't work. Here is code:
<div controller='mainCtr'>
<span click='reset(destination)'>Reset</span>
<div class='floatleft' style='width:30%;margin-right:40px;'>
<smart-Googlemaps locationgoogle='destination.From'></smart-Googlemaps>
<label>From</label>
</div>
</div>
In the directive:
angular.module('ecom').directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '='
},
templateUrl: 'components/directives/autocomplete/googlemap-search.html',
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($(elm).find("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
// $scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
// console.log(place);
$scope.locationgoogle = {};
$scope.locationgoogle.formatted_address = place.formatted_address;
$scope.locationgoogle.loglat = place.geometry.location;
$scope.locationgoogle.locationText = $scope.locationText;
$scope.$apply();
});
}
}
})
Here is html for directive:
<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model='locationText'/>
The directive works fine, I create a isolated scope(locationgoogle) to pass the information I need to parent controller(mainCtr), now in the mainCtr I have a function calld reset(), after I click this,I need to clean up the input make it empty. How Can I do it?
One way to access the value of the model in your directive from a parent controller is to put that on the isolate scope too and use the two-way binding flag = like you've done with the locationgoogle property. Try this:
DEMO
html
<body ng-controller="MainCtrl">
<button ng-click="reset()">Reset</button>
<smart-googlemaps location-text="locationText"></smart-googlemaps>
</body>
js
app.controller('MainCtrl', function($scope) {
// need to define model in parent and pass to directive
$scope.locationText = {
value: ''
};
$scope.reset = function(){
$scope.locationText.value = '';
}
});
app.directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '=',
locationText: '='
},
// ng-model="locationText.value"
template: '<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model="locationText.value"/>',
link: function($scope, elm, attrs){
// implement directive googlemaps logic, set text value etc.
$scope.locationText.value = 'foo';
}
}
})

How to show data returned by AngularJs $asyncValidator

What would be the best way to show data returned by $asyncValidator when form field is valid?
I can show an error besides the input field with ngMessage but would also like to show the resulting response from Restangular when validation passes.
'use strict';
angular.module('app')
.directive('productionOrder', function(Restangular) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, element, attributes, ngModel) {
ngModel.$asyncValidators.exists = function(modelValue) {
return Restangular.one('production_orders', modelValue).get();
}
}
};
});
Html
<form name="workOrderForm">
<input type="text" name="productionOrderNo" ng-model="work_order.work_order.production_order_no" required production-order ng-model-options="{ debounce: 1000 }">
<div ng-messages="workOrderForm.productionOrderNo.$error">
<div ng-message="required">Production order no is mandatory</div>
<div ng-message="exists">Production order not found</div>
</div>
</form>
I'm sure this breaks at least a couple of best practises but this is how I got it working for now.
angular.module('oeeMaster')
.directive('productionOrder', function($q, Restangular) {
return {
restrict: "A",
require: "ngModel",
link: function($scope, element, attributes, ngModel) {
ngModel.$asyncValidators.exists = function(modelValue) {
var deferred = $q.defer();
Restangular.one('production_orders', modelValue).get().then(
function(response) {
ngModel.part_no = response.part_no;
deferred.resolve(response);
}, function() {
ngModel.part_no = null;
deferred.reject();
}
);
return deferred.promise;
}
}
};
});
HTML
<div ng-if="workOrderForm.productionOrderNo.part_no">Part number: {{ workOrderForm.productionOrderNo.part_no }}</div>

Resources