ui-select2 and ng-required - angularjs

I'm having problems using require/ng-require with the ui-select2 component. I wrapped the ui-select2 in a custom directive for reasons of reusability, but can't get the required to work with it.
This is the plunker: http://plnkr.co/edit/2NaGvDWoEw14BN2few9W?p=preview
Tried without the directive and it's working: http://plnkr.co/edit/uE698iFcUNnsYVF2fkOO?p=preview

The Problem was that angular dosn't support dynamic form element:
Issue #1404 https://github.com/angular/angular.js/issues/1404
Workaround (till 1.3 is out):
app.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', ['$delegate', function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
}]);
$provide.decorator('formDirective', ['$delegate', function($delegate) {
var form = $delegate[0], controller = form.controller;
form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
}]);
Source: http://jsfiddle.net/Thinkscape/23RPt/

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.

check attribute from html - angular directive testing jasmine karma

how to check attribute is present in HTML and match its value. this is a test spec.js I wrote,
define(['angular',
'angularMocks',
'site-config',
'ng_detector',
],
function(angular,
mock,
$app,
ng_detector) {
describe('ng-detector controller', function() {
beforeEach(angular.mock.module("webapp"));
var $compile, $rootScope, tpl, $scope, elm, templateAsHtml;
beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
// $scope = _$rootScope_.$new();
}));
it('should initialize the ng-detector directive', inject(function() {
var tpl = $compile("<div ng-detector ></div>")($rootScope);
$rootScope.$digest();
console.log(tpl) // Log: r{0: <div ng-detector="" class="ng-scope" ng-verison="1.6.4"></div>, length: 1}
templateAsHtml = tpl[0].outerHTML;
expect(templateAsHtml.attr('ng-version')).toEqual(angular.version.full);
}));
});
});
directive. that adds angular version to attribute ng-version
'use strict';
define(['app-module'], function(ng) {
$app.info('ng detector initialized. {file: directives/ng-detector.js}');
ng.directive('ngDetector', function() {
return {
restrict: "A",
link: function(scope, elm, attr) {
elm.attr('ng-version', angular.version.full);
}
};
});
return ng;
});
I want to get a ng-version attribute set by the directive and match the attribute value.
I figured out myself. I was looking at the different place.
it('should check the angular version number', angular.mock.inject(function() {
expect(tpl.attr('ng-version')).toEqual(angular.version.full);
}));

Call a function after the directive is loaded angularjs

I have a isolated directive, my controller looks like:
app.controller('ZacksController', ['$scope', '$http', 'ngDialog', '$timeout', function($scope, $http, ngDialog, $timeout){
//some code here
}]);
The HTML in the file looks like:
<div class="income-older-block" ng-show="selectedAge!=1">
<income-form></income-form>
</div>
I have a directive in related HTML folder,
app.directive("incomeForm", ['$timeout', function ($timeout) {
function link($scope) {
var hello = function () {
alert("1");
}
$timeout(hello, 0);
}
return {
restrict: 'E',
templateUrl: "app/zacks/your-income/income-form/income-form.html",
link: link,
controller: function ($scope, $timeout) {
$scope.$watch("zacks.AgeRet.value",function(newValue,OldValue,scope){
if (newValue){
alert((newValue));
}
});
}
}
}]);
I want to alert after I load the directive in the page, the alert appears at initial page itself. May I know what to do?
The actual problem is I'm using a rz-slider and want to initialize it once the directive is loaded to DOM., as its not taking the values provided. Is there any other approach for this problem?
<rzslider rz-slider-model="zacks.AgeRet.value" rz-slider-floor="zacks.AgeRet.floor" rz-slider-ceil="zacks.AgeRet.ceil"></rzslider>
In case if the timeout works, I'm planning to initialize something like this:
$timeout(function () {
$scope.$broadcast('rzSliderForceRender');
});
UPDATE
Added a controller in the directive, so now I'm able to get the value when I move the slider, but still not able to initialize the value of the slider.
I kind of found 2 solutions for these kind of problems, but still not serving the purpose of what I really need with rz-slider.
Solution 1
HTML:
<div ng-controller="ZacksController">
<after-render after-render="rzSliderForceRender">element</after-render>
</div>
JS:
var app = angular.module('myApp',[]);
app.directive('afterRender', ['$timeout', function ($timeout) {
var def = {
restrict: 'E',
link: function (scope, element, attrs) {
$timeout(scope.$eval(attrs.afterRender), 0); //Calling a scoped method
}
};
return def;
}]);
app.controller('ZacksController', ['$rootScope', '$scope', '$http', ' $timeout', function($rootScope, $scope, $http, $timeout){
$scope.rzSliderForceRender = function()
{
alert('Fired!');
};
}]);
Solution 2
HTML:
<div class="income-older-block" ng-show="selectedAge!=1">
<income-form></income-form>
</div>
JS:
app.controller('ZacksapiController', ['$rootScope', '$scope', '$http', 'ngDialog', '$timeout', 'dataService', function($rootScope, $scope, $http, ngDialog, $timeout, dataService){
$scope.$watch('selectedAge', function(newValue, oldValue) {
if (newValue !== oldValue) {
$timeout(function() {
alert("reCalcViewDimensions");
$scope.$broadcast('rzSliderForceRender'); // This is not working, but alert works.
}, 0);
}
});
Update:
Triggered the window resize event inside the $timeout, but this would be a temporary hack. Would be great if someone help me out with the real approach to solve the problem.
$timeout(function() {
window.dispatchEvent(new Event('resize'));
$scope.$broadcast('rzSliderForceRender');
}, 0);

Angular call directive function from template

I'm new to angular and can't figure out how to call a directive function from the template. I have some fuctionality that will be reused throught the app and figured I would just make a directive with all the functionlity needed, that can easily be shared accross different modules. While searching for answers I came across this post: how-to-call-a-method-defined-in-an-angularjs-directive
which seems like a good solution. However, I can't seem to figure out why my directive method showPolicy() is not being called.
// controller:
(function(){
'use strict';
angular.module('releaseAppsModule')
.controller('releaseAppsController', releaseAppsController);
releaseAppsController.$inject = ['$rootScope',
'storageFactory',
'releaseAppsFactory',
'$modal',
'$translate',
'getIconFactory',
'$scope',
'$filter'];
function releaseAppsController($rootScope, storageFactory, releaseAppsFactory, $modal, $translate, getIconFactory, $scope, $filter) {
var vm = this;
vm.policyControl = {};
...
// controller template:
<tr ng-repeat="policyRelease in regionRelease.policyReleases | orderBy:vm.orderByField:vm.reverseSort" ng-if="policyRelease.status == 'NEW' || policyRelease.status == 'SCHEDULED'">
<td>
<policy control="vm.policyControl" release-item="policyRelease" class="release-apps-app-btn app-release-data"></policy>
</td>
// directive:
(function(){
'use strict';
angular.module('myApp')
.directive('policy', policy)
function policy() {
var directive = {
restrict: 'E',
link: link,
replace: true,
scope: {
releaseItem: '=',
control: '='
},
template: '<a ng-click="vm.policyControl.showPolicy({releaseItem: releaseItem});">{{ releaseItem.policy.name }}</a>'
};
return directive;
function link(scope, el, attr) {
scope.internalControl = scope.control || {};
scope.internalControl.showPolicy = function (releaseData) {
...
} // showPolicy
scope.internalControl.showPolicyModal = function(response, releaseData) {
...
} // showPolicyModal
} // link
} // policy
})();
In your template, you're trying to call vm.policyControl.showPolicy() which is undefined on your current directive scope, as Angular is attempting to find
[directiveScope].vm.policyControl.showPolicy()
You'll need to change the ng-click function to internalControl.showPolicy(), as that is referencing the actual object that the directive's scope has available.

AngularJS: Error: No controller: form

HTML:
<div ng-app="my-app" ng-controller="AppController">
<ng-form name="myform">
<gtux-el></gtux-el>
</ng-form>
</div>
JS
var app = angular.module('my-app', [], function () {
})
app.controller('AppController', function ($scope) {
})
app.directive('gtInputMsg', ['$compile', '$interpolate', '$log', function($compile, $interpolate, $log) {
function link($scope, element, attrs, ctrls) {
var modelCtrl = ctrls[0], formCtrl = ctrls[1], msgCtrl = ctrls[2];
element.on('click', function() {
console.log('gt-input-msg:click', element)
});
};
return {
require : ['ngModel', '^form', 'gtInputMsg'],
link : link
};
}]);
app.directive('gtuxTextfield', ['$compile', '$timeout', '$log', function($compile, $timeout, $log) {
return {
restrict : 'E',
template : '<input type="text" name="field" gt-input-msg ng-model="fieldvalue" />',
replace : true
};
}]);
app.directive('gtuxEl', ['$compile', '$timeout', '$log', function($compile, $timeout, $log) {
function link($scope, $element, attrs, ctrl) {
//here compile is used because in my use case different elements are used based on some option values. A service provides the template to be used based on a key
var $cr = $compile('<gtux-textfield></gtux-textfield>')($scope);
$($cr).appendTo($element);
}
return {
restrict : 'E',
link : link,
transclude : true
};
}]);
Error
Error: No controller: form
at Error (<anonymous>)
at getControllers (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:4278:19)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:4284:24
Demo: Fiddle
As far I can see there is a ng-form element at the top of the hierarchy which should provide the from controller to the gtInputMsg directive.
How can this be rectified?
You're compiling the <gtux-textfield> element without it being under the form in the DOM as you haven't appended it yet. Try:
var $cr = $('<gtux-textfield></gtux-textfield>').appendTo($element);
$compile($cr)($scope);
In your gtuxEl link function.
Demo: Fiddle

Resources