Angular 1.2 Broadcasting changes in validation across directives - angularjs

How do I use $broadcast to update custom validation in other directives which already have isolated scope?
I want to be able create separate validation rules used on a single input field.So in the future I can change the validation of a field by simply changing the directive reference.
Check the plunkr
edit: I am using angular 1.2.8
The element the directive is on has isolated scope.
Validation Directive 1
(function () {
'use strict';
angular
.module('app')
.directive('dateOneValidation', dateOneValidation);
function dateOneValidation() {
var directive = {
require: 'ngModel', // note: this has to stay
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs, ctrl) {
scope.$on('updateDateOneValidation', function(e, date){
ctrl.$parsers.unshift(function (viewValue) {
var form = scope.form;
var dateOne = moment(form.dateOne.$viewValue, "DD/MM/YYYY", true);
var today = moment();
var dateOneBeforeOrOnToday = dateOne.isSame(today, 'day') || dateOne.isBefore(today, 'day');
dateOneBeforeOrOnToday ? form.dateOne.$setValidity('dateOneBeforeOrOnToday', true):
form.dateOne.$setValidity('dateOneBeforeOrOnToday', false);
return viewValue
});
});
}
}
})();
Validation Directive 2
(function () {
'use strict';
angular
.module('app')
.directive('dateTwoValidation', dateTwoValidation);
function dateTwoValidation() {
var directive = {
require: 'ngModel', // note: this has to stay
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs, ctrl) {
scope.$on('updateDateTwoValidation', function(e, date){
ctrl.$parsers.unshift(function (viewValue) {
var form = scope.form;
var dateOne = moment(form.dateOne.$viewValue, "DD/MM/YYYY", true);
var dateTwo = moment(viewValue, "DD/MM/YYYY", true);
var dateTwoAfterDateOne = dateTwo.isSame(dateOne, 'day') || dateTwo.isAfter(dateOne, 'day');
dateTwoAfterDateOne ? form.dateTwo.$setValidity('dateTwoAfterDateOne', true):
form.dateTwo.$setValidity('dateTwoAfterDateOne', false);
return viewValue
});
});
}
}
})();

(function () {
'use strict';
angular
.module('app')
.directive('stepOne', stepOne);
function stepOne() {
parentController.$inject = ['$scope'];
function parentController($scope) {
var vm = this;
vm.dateOne = '01/01/2000'
vm.dateTwo = '01/01/1900'
vm.validateStepOne = validateStepOne;
function validateStepOne() {
$scope.$broadcast('updateDateOneValidation');
$scope.$broadcast('updateDateTwoValidation');
}
}
var directive = {
restrict: 'EA',
require: '^form',
templateUrl: 'src/app/form/step1.html',
scope: {
},
controller: parentController,
controllerAs: 'vm'
};
return directive;
}
})();
(function () {
'use strict';
angular
.module('app')
.directive('dateOneValidation', dateOneValidation);
function dateOneValidation() {
var directive = {
require: 'ngModel', // note: this has to stay
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs, ctrl) {
var form = scope.form;
var today = moment();
scope.$watch(attrs.ngModel, function () {
validator()
});
scope.$on('updateDateOneValidation', function () {
validator();
});
function validator() {
var dateOne = moment(form.dateOne.$viewValue, "DD/MM/YYYY", true);
var dateOneBeforeOrOnToday = dateOne.isSame(today, 'day') || dateOne.isBefore(today, 'day');
dateOneBeforeOrOnToday ? form.dateOne.$setValidity('dateOneBeforeOrOnToday', true) :
form.dateOne.$setValidity('dateOneBeforeOrOnToday', false);
}
}
}
})();
(function () {
'use strict';
angular
.module('app')
.directive('dateTwoValidation', dateTwoValidation);
function dateTwoValidation() {
var directive = {
require: 'ngModel', // note: this has to stay
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs, ctrl) {
var form = scope.form;
scope.$watch(attrs.ngModel, function () {
validator();
});
scope.$on('updateDateTwoValidation', function (e, date) {
validator();
});
function validator() {
var dateOne = moment(form.dateOne.$viewValue, "DD/MM/YYYY", true);
var dateTwo = moment(form.dateTwo.$viewValue, "DD/MM/YYYY", true);
var dateTwoAfterDateOne = dateTwo.isSame(dateOne, 'day') || dateTwo.isAfter(dateOne, 'day');
dateTwoAfterDateOne ? form.dateTwo.$setValidity('dateTwoAfterDateOne', true) :
form.dateTwo.$setValidity('dateTwoAfterDateOne', false);
};
};
}
})()

Alternatively You can use a higher shared scope with a form object and pass it to your directives. Something like the following:
topLevelScope - ngForm
directive1(topLevelScope.ngForm)
topLevelScope.ngForm.$setValidity('input1', true)
directive2(topLevelScope.ngForm)
topLevelScope.ngForm.$setValidity('input2', true)
directive3(topLevelScope.ngForm)
topLevelScope.ngForm.$setValidity('input3', true)
My 2 cents.

Related

Minification safe directive with link attribute in angular js

There is very handly directive at to format date in ng-model with HTML input here.
angular.module('app', []).controller('Ctrl', function($scope){
$scope.firstDate = new Date();
$scope.secondDate = "2014-02-20";
}).directive('date', function (dateFilter) {
return {
require:'ngModel',
link:function (scope, elm, attrs, ctrl) {
var dateFormat = attrs['date'] || 'yyyy-MM-dd';
ctrl.$formatters.unshift(function (modelValue) {
return dateFilter(modelValue, dateFormat);
});
}
};
});
I tried to use same for my project,but the problem is that is fails to work when javascript file is minified. It logs an error [$injector:unpr] ...../$injector/unpr?p0=eProvider%20%3C-%20e%20%3C-%20dateDirective.
I tried to minify using this
Please guide me on how to minify this directive.
It happens because you don't use injection a.e $inject.
.controller('Ctrl', ['$scope',function($scope){/* ... */}]);
.directive('date', ['dateFilter', function (dateFilter) {/* ... */}]);
So your directive (and controller) will look like:
angular.module('app', []).controller('Ctrl', ['$scope',function($scope){
$scope.firstDate = new Date();
$scope.secondDate = "2014-02-20";
}]).directive('date', ['dateFilter', function (dateFilter) {
return {
require:'ngModel',
link:function (scope, elm, attrs, ctrl) {
var dateFormat = attrs['date'] || 'yyyy-MM-dd';
ctrl.$formatters.unshift(function (modelValue) {
return dateFilter(modelValue, dateFormat);
});
}
};
}]);

Access angular controller from directive using require

In a directive i want to require a controller but i get the error that the controller can't be found. I am sure it is a small thing or maybe it is not possible the way i want to do it.
angular.module('myApp', []);
angular.module('myApp').controller('GreetingController', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
//some function here returning data
}]);
angular.module('myApp').directive('yoloswag', function() {
return {
require: ['^?ngModel', '^GreetingController'],
restrict: 'A',
scope: {
},
link: function(scope, element, attrs, controllers) {
var modelCtrl = controllers[0],
greetingsCtrl = controllers[1];
console.log(controllers)
}
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="GreetingController">
{{ greeting }}
<div yoloswag>test</div>
</div>
</div>
What am i doing wrong?
Thank you so much!
Your code does not having and dependency with your main module, that's why you are getting that error.
Your code should be the following
angular.module('myApp', [])
.controller('GreetingController', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
//some function here returning data
}])
.directive('yoloswag', function() {
return {
require: ['^?ngModel', '^GreetingController'],
restrict: 'A',
scope: {
},
link: function(scope, element, attrs, controllers) {
var modelCtrl = controllers[0],
greetingsCtrl = controllers[1];
console.log(controllers)
}
};
});
or set a variable for main module then you can add controller and directive with the main module like,
var MainModule = angular.module('myApp', []);
MainModule.controller('GreetingController', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
//some function here returning data
}]);
MainModule.directive('yoloswag', function() {
return {
require: ['^?ngModel', '^GreetingController'],
restrict: 'A',
scope: {
},
link: function(scope, element, attrs, controllers) {
var modelCtrl = controllers[0],
greetingsCtrl = controllers[1];
console.log(controllers)
}
};
});
You can use controller property of directive :
make sure don't update property in link function.
return {
restrict: 'A',
scope: {
},
controller : 'GreetingController',
link: function(scope, element, attrs) {
var modelCtrl = controllers[0],
greetingsCtrl = controllers[1];
console.log(controllers)
}
};

How can I write directive for input and text area

I wrote a directive for input focus & blur
angular
.module('app')
.directive('input', ['$filter', function($filter) {
return function(scope, element, attrs) {
if (element && element[0] && element[0].placeholder) {
scope.placeholder = element[0].placeholder;
element.bind("focus", function() {
console.log(scope.placeholder);
element[0].placeholder = "";
});
element.bind("blur", function() {
element[0].placeholder = $filter('translate')(scope.placeholder);
});
}
};
}]);
I want same functionality for text area also. But don't wanted to write an other directive. How I can do this?
var myDirective = ['$filter', function($filter) {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
if (element && element[0] && element[0].placeholder) {
scope.placeholder = element[0].placeholder;
element.bind("focus", function() {
console.log(scope.placeholder);
element[0].placeholder = "";
});
element.bind("blur", function() {
element[0].placeholder = $filter('translate')(scope.placeholder);
});
}
}
};
}]
angular
.module('app')
.directive('input', myDirective);
.directive('textarea', myDirective);
and in your html:
<input />
<textarea></textarea>

Dynamically add ngModels to child elements in angular directive

Have some directive to uncheck previously checked radio in group:
(function (angular, $) {
'use strict';
var radioGroupDirective = function () {
return {
restrict: 'EA',
require: 'ngModel',
link: function($scope, $element, $attrs, ngModelController) {
var $radios = $element.find('input[type="radio"]');
$radios.click(function($event) {
var $radio = $($event.target);
if ($radio.data('waschecked') == true) {
$radio.prop('checked', false);
$radio.data('waschecked', false);
ngModelController.$setViewValue(null);
} else {
$radio.data('waschecked', true);
}
$radio.siblings('input[type="radio"]').data('waschecked', false);
});
},
};
};
radioGroupDirective.$inject = [];
angular.module('radio.group', []).directive('radioGroup', radioGroupDirective);
})(angular, $);
Usage:
<div radio-group ng-model="fruit">
<input type="radio" ng-model="fruit" value="Apple"/>
<input type="radio" ng-model="fruit" value="Banana"/>
<input type="radio" ng-model="fruit" value="Mango"/>
</div>
It works fine, but I want to remove duplicate code of ngModels in child inputs. Like this:
<div radio-group ng-model="fruit">
<input type="radio" value="Apple"/>
<input type="radio" value="Banana"/>
<input type="radio" value="Mango"/>
</div>
So I try to add ngModel to all child inputs dynamically at compile function
(function (angular, $) {
'use strict';
var radioGroupDirective = function ($compile) {
return {
restrict: 'EA',
require: 'ngModel',
link: function($scope, $element, $attrs, ngModelController) {
var $radios = $element.find('input[type="radio"]');
$radios.click(function($event) {
var $radio = $($event.target);
if ($radio.data('waschecked') == true) {
$radio.prop('checked', false);
$radio.data('waschecked', false);
ngModelController.$setViewValue(null);
} else {
$radio.data('waschecked', true);
}
$radio.siblings('input[type="radio"]').data('waschecked', false);
});
},
compile: function (tElement, tAttrs) {
var $radios = tElement.find('input[type="radio"]');
angular.forEach($radios, function(radio) {
$(radio).attr('ng-model', tAttrs.ngModel);
});
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
$compile(iElement)(scope);
},
};
},
};
};
radioGroupDirective.$inject = ['$compile'];
angular.module('radio.group', []).directive('radioGroup', radioGroupDirective);
})(angular, $);
but it causes an infinite compilation loop and a dead of the browser
You try to compile the entire the directive (radioGroup) again from the link function so it causes an infinite loop.
Instead compile only the inputs:
angular.forEach($radios, function(radio) {
$compile(radio)(scope);
});
See this plunker.
Full worked plunker for this directive (for someone who could find it usefull)
var radioGroupDirective = function ($compile) {
return {
restrict: 'EA',
require: 'ngModel',
compile: function (tElement, tAttrs) {
var $radios = tElement.find('input');
angular.forEach($radios, function(radio) {
$(radio).attr('ng-model', tAttrs.ngModel);
});
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
angular.forEach($radios, function(radio) {
$compile(radio)(scope);
});
$($radios).click(function($event) {
var $radio = $($event.target);
if ($radio.data('waschecked') == true) {
$radio.prop('checked', false);
$radio.data('waschecked', false);
controller.$setViewValue(null);
} else {
$radio.data('waschecked', true);
}
$radio.siblings('input[type="radio"]').data('waschecked', false);
});
},
};
},
};
};
radioGroupDirective.$inject = ['$compile'];
angular.module('radio.group', []).directive('radioGroup', radioGroupDirective);

AngularJs passing a value to a directive

I'm trying to pass a value to a directive. The directive is used to integrate a jquery plugin Knob
JSFIDDLE: http://jsfiddle.net/Tropicalista/TH87t/93/
I have this code:
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.number = 24;
})
App.directive('knob', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).knob().val(scope.number);
console.log(attrs)
}
};
});
The problem is that knob() doesn't return the element. Use this instead:
link: function(scope, element, attrs) {
$(element).val(scope.number).knob();
}
Here's your fiddle: http://jsfiddle.net/TH87t/94/
I used your question as reference for a fully bi-direction binding. For a working version with angular 1.2.1 see http://jsfiddle.net/sander_van_dam/m5YJu/
App.directive('knob', function() {
return {
require: 'ngModel',
scope: { model: '=ngModel' },
controller: function($scope, $element, $timeout) {
var el = $($element);
$scope.$watch('model', function(v) {
var el = $($element);
el.val(v).trigger('change');
});
},
link: function($scope, $element, $attrs,$ngModel) {
var el = $($element);
el.val($scope.value).knob(
{
'change' : function (v) {
$scope.$apply(function () {
$ngModel.$setViewValue(v);
});
}
}
);
}
}
});

Resources