Access directive's controller function inside link function - angularjs

How to access the directives controller functions in link? This returns error:
ReferenceError: getFormatedValue is not defined.
Saw some examples where one injects a seperate controller through require and is able to access the controller by injecting it, as seen below in my code, as the 4th param in the link function. However, i need this controller to be inside this directive.
.directive('testDirective', function () {
link: function ($scope, element, attribute, controller) {
attribute.$observe('numbers', function(value) {
controller.setNumbers(value);
});
attribute.$observe('decimals', function(decimals) {
decimals = decimals || defaultValue();
decimals = (decimals.length > 2 ? decimals.slice(0, 2 - decimals.length) : decimals);
$scope.decimals = controller.getFormatedValue(decimals, 'decimals');
});
},
controller: function ($scope, $element, $attrs) {
this.setNumbers = function (numbers) {
numbers = numbers || $scope.defaultValue();
$scope.numbers = getFormatedValue(numbers, 'numbers');
};
this.getFormatedValue = function(value, kindofvalue){
var maxDecimals = '2',
returnValue;
if (kindofvalue === 'decimals'){
returnValue = addzero(value, maxDecimals);
}
else if (kindofvalue === 'numbers'){
returnValue = value.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
}
return returnValue;
};
this.defaultValue = function() {
return '0';
};
this.addzero = function(decimalValue , maxDecimals) {
return decimalValue.length < maxDecimals ? $scope.addzero(decimalValue + '0', maxDecimals) : decimalValue;
};
}
};

Add require: 'testDirective' to the directive definition, i.e.:
.directive('testDirective', function () {
return {
require: 'testDirective',
link: function ($scope, element, attribute, controller) {
...
},
controller: function ($scope, $element, $attrs) {
...
}
};
});

Related

Angularjs, Range.insertNode, ngClick : After $compile Angular isn't aware of element - ngClick not working

ngClick is still not responding even after $compile. The new element is being applied to the DOM and is accessible via jQuery and JS. I assume that the issue is with the range.insertNode function. What am I missing here?
Here's my directive:
.directive('selectText', [
'$rootScope',
'$compile',
'$window',
function ($rootScope, $compile, $window) {
return {
restrict: 'A',
scope: {
hlid: "=",
tu: "="
},
link: function (scope, element, attrs) {
element.on('mouseup', function () {
//console.log("Attrs: "+JSON.stringify(attrs));
if ($window.getSelection().toString()) {
var text = $window.getSelection().toString();
if(text == '') {
console.log("No selection");
return;
}
var selection = $window.getSelection();
var range = selection.getRangeAt(0);
var selectionContents = range.extractContents();
var clk = "edSel('hl_"+scope.hlid+"','"+attrs.id+"');";
// var span = $compile(angular.element('<hlight id="hl_'+scope.hlid+'" class="cr-pr noselect clickable" title="Text Selection" ng-click="'+clk+'">'+text+'</hlight>'))(scope);
var span = angular.element($compile('<hlight id="hl_'+scope.hlid+'" class="cr-pr noselect clickable" title="Text Selection" ng-click="'+clk+'">'+text+'</hlight>')(scope));
console.log(span);
range.insertNode(span[0]);
scope.tu.target = element.html();
//selection.removeAllRanges();
var arr = {};
arr.action = 'add';
arr.tuid = attrs.id;
arr.hlid = 'hl_'+scope.hlid;
arr.content = element.html();
scope.$emit('hlChange', arr);
scope.hlid++;
console.log(element.html());
var modal = UIkit.modal("#hl_modal");
modal.show();
}
});
scope.edSel = function(id,tuid) {
console.log('ID: '+id+" - tuID: "+tuid);
}
}
};
}])
Thanks for any help

How to pass `controllers` status to `directive` template function?

Accoding to the current page, i need to change the template. my question is, how to pass the current page from controller to directives template method?
here is my try:
var myApp = angular.module('myApp', []);
myApp.controller('main', function ($scope) {
$scope.template = "homePage";
});
var getTemplate = function (page) { //i need $scope.template as params
if (page == "homePage") {
return "<button>One Button</button>"
}
if (page == "servicePage") {
return "<button>One Button</button><button>Two Button</button>"
}
if (page == "homePage") {
return "<button>One Button</button><button>Two Button</button><button>Three Button</button>"
}
}
myApp.directive('galleryMenu', function () {
return {
template : getTemplate(template), //$scope.template need to pass
link : function (scope, element, attrs) {
console.log(scope.template);
}
}
})
Live Demo
UPDATE
I am trying like this, but still getting error. what is the correct way to inject the $route to directive?
var galleryMenu = function ($route, $location) {
return {
template : function () {
console.log($route.current.className); //i am not getting!
},
link : function () {
}
}
}
angular
.module("tcpApp", ['$route', '$location'])
.directive('galleryMenu', galleryMenu);
You can call $routeParams on your directive declaration, to use it inside the template function.
myApp.directive('galleryMenu', ['$routeParams', function($routeParams) {
return {
template: function () {
var page = $routeParams.page || 'homePage', // Define a fallback, if $routeParams doesn't have 'page' param
output;
switch (page) {
case "servicePage":
output = "<button>One Button</button><button>Two Button</button>";
break;
default:
case "homePage":
output = "<button>One Button</button>";
/*
NOTE: Or this other, it was confusing to tell which one to use
output = "<button>One Button</button><button>Two Button</button><button>Three Button</button>";
*/
break;
}
return output;
},
link: function(scope, element, attrs) {
/* ... */
}
}
}]);
Edit 1:
If you are using ui-router switch from $routeParams to $stateParams.
You need to get current url from $state.current and can pass into the directive with the help of templateProvider.
myApp.directive('galleryMenu', function () {
return {
templateProvider : getTemplate(template), //$scope.template need to pass
link : function (scope, element, attrs) {
console.log(scope.template);
}
}
from getTemplate you can return $state.current. hope so it'll help you.

angularjs directive 2 way binding not working

Here is my directive, it's simple task is to Locale Date String time:
.directive('localeDateString',['$window', function ($window) {
return {
restrict: 'E',
replace: true,
scope: {
time: '='
},
template: '<span>{{timeLocal}}</span>',
link: function ($scope, $element) {
if ($scope.time != null) {
profileDate = new Date($scope.time);
var cultureCode = $window.ApiData.CultureCode;
$scope.timeLocal = profileDate.toLocaleDateString(cultureCode);
}
}
};
}])
Usage in HTML:
<li ng-repeat="note in profile.AccountProfile.Notes" class="noteItem">
<locale-date-string time="note.Created" ></locale-date-string>
<span>{{note.UserName}}</span>
<!-- other stuff .. -->
</li>
When I'm loading the object "profile" from JSON everything is OK
the problem is when i change "note.Created" from controller - the directive seem not to work(other members of Note are updating ok):
In the controller:
DataService.updateProfileRemark(objRemark)
.then(function (response) {
// all is ok;
var profileIndex = $scope.ProfileList.indexOf(profile);
var noteIndex = $scope.ProfileList[profileIndex].AccountProfile.Notes.indexOf(note);
// this is working:
$scope.ProfileList[profileIndex].AccountProfile.Notes[noteIndex].UserName = objRemark.UserName;
// this is not:
$scope.ProfileList[profileIndex].AccountProfile.Notes[noteIndex].Created = Date.now();
},
function (errResponse) {
// handle err
}
);
For example, here is the scope before "updateProfileRemark":
and after:
Why the 2 way binding not working?
Thanks.
link is only executed once. If you want to setup two-way binding between $scope.timeLocal and $scope.time, setup a $watch:
link: function ($scope, $element) {
$scope.$watch('time', function(newTime) {
if (newTime != null) {
var profileDate = new Date(newTime);
var cultureCode = $window.ApiData.CultureCode;
$scope.timeLocal = profileDate.toLocaleDateString(cultureCode);
}
});

AngularJS - bind to directive resize

How can i be notified when a directive is resized?
i have tried
element[0].onresize = function() {
console.log(element[0].offsetWidth + " " + element[0].offsetHeight);
}
but its not calling the function
(function() {
'use strict';
// Define the directive on the module.
// Inject the dependencies.
// Point to the directive definition function.
angular.module('app').directive('nvLayout', ['$window', '$compile', layoutDirective]);
function layoutDirective($window, $compile) {
// Usage:
//
// Creates:
//
var directive = {
link: link,
restrict: 'EA',
scope: {
layoutEntries: "=",
selected: "&onSelected"
},
template: "<div></div>",
controller: controller
};
return directive;
function link(scope, element, attrs) {
var elementCol = [];
var onSelectedHandler = scope.selected();
element.on("resize", function () {
console.log("resized.");
});
$(window).on("resize",scope.sizeNotifier);
scope.$on("$destroy", function () {
$(window).off("resize", $scope.sizeNotifier);
});
scope.sizeNotifier = function() {
alert("windows is being resized...");
};
scope.onselected = function(id) {
onSelectedHandler(id);
};
scope.$watch(function () {
return scope.layoutEntries.length;
},
function (value) {
//layout was changed
activateLayout(scope.layoutEntries);
});
function activateLayout(layoutEntries) {
for (var i = 0; i < layoutEntries.length; i++) {
if (elementCol[layoutEntries[i].id]) {
continue;
}
var div = "<nv-single-layout-entry id=slot" + layoutEntries[i].id + " on-selected='onselected' style=\"position:absolute;";
div = div + "top:" + layoutEntries[i].position.top + "%;";
div = div + "left:" + layoutEntries[i].position.left + "%;";
div = div + "height:" + layoutEntries[i].size.height + "%;";
div = div + "width:" + layoutEntries[i].size.width + "%;";
div = div + "\"></nv-single-layout-entry>";
var el = $compile(div)(scope);
element.append(el);
elementCol[layoutEntries[i].id] = 1;
}
};
}
function controller($scope, $element) {
}
}
})();
Use scope.$watch with a custom watch function:
scope.$watch(
function () {
return [element[0].offsetWidth, element[0].offsetHeight].join('x');
},
function (value) {
console.log('directive got resized:', value.split('x'));
}
)
You would typically want to watch the element's offsetWidth and offsetHeight properties. With more recent versions of AngularJS, you can use $scope.$watchGroup in your link function:
app.directive('myDirective', [function() {
function link($scope, element) {
var container = element[0];
$scope.$watchGroup([
function() { return container.offsetWidth; },
function() { return container.offsetHeight; }
], function(values) {
// Handle resize event ...
});
}
// Return directive definition ...
}]);
However, you may find that updates are quite slow when watching the element properties directly in this manner.
To make your directive more responsive, you could moderate the refresh rate by using $interval. Here's an example of a reusable service for watching element sizes at a configurable millisecond rate:
app.factory('sizeWatcher', ['$interval', function($interval) {
return function (element, rate) {
var self = this;
(self.update = function() { self.dimensions = [element.offsetWidth, element.offsetHeight]; })();
self.monitor = $interval(self.update, rate);
self.group = [function() { return self.dimensions[0]; }, function() { return self.dimensions[1]; }];
self.cancel = function() { $interval.cancel(self.monitor); };
};
}]);
A directive using such a service would look something like this:
app.directive('myDirective', ['sizeWatcher', function(sizeWatcher) {
function link($scope, element) {
var container = element[0],
watcher = new sizeWatcher(container, 200);
$scope.$watchGroup(watcher.group, function(values) {
// Handle resize event ...
});
$scope.$on('$destroy', watcher.cancel);
}
// Return directive definition ...
}]);
Note the call to watcher.cancel() in the $scope.$destroy event handler; this ensures that the $interval instance is destroyed when no longer required.
A JSFiddle example can be found here.
Here a sample code of what you need to do:
APP.directive('nvLayout', function ($window) {
return {
template: "<div></div>",
restrict: 'EA',
link: function postLink(scope, element, attrs) {
scope.onResizeFunction = function() {
scope.windowHeight = $window.innerHeight;
scope.windowWidth = $window.innerWidth;
console.log(scope.windowHeight+"-"+scope.windowWidth)
};
// Call to the function when the page is first loaded
scope.onResizeFunction();
angular.element($window).bind('resize', function() {
scope.onResizeFunction();
scope.$apply();
});
}
};
});
The only way you would be able to detect size/position changes on an element using $watch is if you constantly updated your scope using something like $interval or $timeout. While possible, it can become an expensive operation, and really slow your app down.
One way you could detect a change on an element is by calling
requestAnimationFrame.
var previousPosition = element[0].getBoundingClientRect();
onFrame();
function onFrame() {
var currentPosition = element[0].getBoundingClientRect();
if (!angular.equals(previousPosition, currentPosition)) {
resiszeNotifier();
}
previousPosition = currentPosition;
requestAnimationFrame(onFrame);
}
function resiszeNotifier() {
// Notify...
}
Here's a Plunk demonstrating this. As long as you're moving the box around, it will stay red.
http://plnkr.co/edit/qiMJaeipE9DgFsYd0sfr?p=preview
A slight variation on Eliel's answer worked for me. In the directive.js:
$scope.onResizeFunction = function() {
};
// Call to the function when the page is first loaded
$scope.onResizeFunction();
angular.element($(window)).bind('resize', function() {
$scope.onResizeFunction();
$scope.$apply();
});
I call
$(window).resize();
from within my app.js. The directive's d3 chart now resizes to fill the container.
Here is my take on this directive (using Webpack as bundler):
module.exports = (ngModule) ->
ngModule.directive 'onResize', ['Callback', (Callback) ->
restrict: 'A'
scope:
onResize: '#'
onResizeDebounce: '#'
link: (scope, element) ->
container = element[0]
eventName = scope.onResize || 'onResize'
delay = scope.onResizeDebounce || 1000
scope.$watchGroup [
-> container.offsetWidth ,
-> container.offsetHeight
], _.debounce (values) ->
Callback.event(eventName, values)
, delay
]

angular directive encapsulating a delay for ng-change

I have a search input field with a requery function bound to the ng-change.
<input ng-model="search" ng-change="updateSearch()">
However this fires too quickly on every character. So I end up doing something like this alot:
$scope.updateSearch = function(){
$timeout.cancel(searchDelay);
searchDelay = $timeout(function(){
$scope.requery($scope.search);
},300);
}
So that the request is only made 300ms after the user has stopped typing. Is there any solution to wrap this in a directive?
As of angular 1.3 this is way easier to accomplish, using ngModelOptions:
<input ng-model="search" ng-change="updateSearch()" ng-model-options="{debounce:3000}">
Syntax: {debounce: Miliseconds}
To solve this problem, I created a directive called ngDelay.
ngDelay augments the behavior of ngChange to support the desired delayed behavior, which provides updates whenever the user is inactive, rather than on every keystroke. The trick was to use a child scope, and replace the value of ngChange to a function call that includes the timeout logic and executes the original expression on the parent scope. The second trick was to move any ngModel bindings to the parent scope, if present. These changes are all performed in the compile phase of the ngDelay directive.
Here's a fiddle which contains an example using ngDelay:
http://jsfiddle.net/ZfrTX/7/ (Written and edited by me, with help from mainguy and Ryan Q)
You can find this code on GitHub thanks to brentvatne. Thanks Brent!
For quick reference, here's the JavaScript for the ngDelay directive:
app.directive('ngDelay', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: true,
compile: function (element, attributes) {
var expression = attributes['ngChange'];
if (!expression)
return;
var ngModel = attributes['ngModel'];
if (ngModel) attributes['ngModel'] = '$parent.' + ngModel;
attributes['ngChange'] = '$$delay.execute()';
return {
post: function (scope, element, attributes) {
scope.$$delay = {
expression: expression,
delay: scope.$eval(attributes['ngDelay']),
execute: function () {
var state = scope.$$delay;
state.then = Date.now();
$timeout(function () {
if (Date.now() - state.then >= state.delay)
scope.$parent.$eval(expression);
}, state.delay);
}
};
}
}
}
};
}]);
And if there are any TypeScript wonks, here's the TypeScript using the angular definitions from DefinitelyTyped:
components.directive('ngDelay', ['$timeout', ($timeout: ng.ITimeoutService) => {
var directive: ng.IDirective = {
restrict: 'A',
scope: true,
compile: (element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
var expression = attributes['ngChange'];
if (!expression)
return;
var ngModel = attributes['ngModel'];
if (ngModel) attributes['ngModel'] = '$parent.' + ngModel;
attributes['ngChange'] = '$$delay.execute()';
return {
post: (scope: IDelayScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
scope.$$delay = {
expression: <string>expression,
delay: <number>scope.$eval(attributes['ngDelay']),
execute: function () {
var state = scope.$$delay;
state.then = Date.now();
$timeout(function () {
if (Date.now() - state.then >= state.delay)
scope.$parent.$eval(expression);
}, state.delay);
}
};
}
}
}
};
return directive;
}]);
interface IDelayScope extends ng.IScope {
$$delay: IDelayState;
}
interface IDelayState {
delay: number;
expression: string;
execute(): void;
then?: number;
action?: ng.IPromise<any>;
}
This works perfectly for me: JSFiddle
var app = angular.module('app', []);
app.directive('delaySearch', function ($timeout) {
return {
restrict: 'EA',
template: ' <input ng-model="search" ng-change="modelChanged()">',
link: function ($scope, element, attrs) {
$scope.modelChanged = function () {
$timeout(function () {
if ($scope.lastSearch != $scope.search) {
if ($scope.delayedMethod) {
$scope.lastSearch = $scope.search;
$scope.delayedMethod({ search: $scope.search });
}
}
}, 300);
}
},
scope: {
delayedMethod:'&'
}
}
});
Using the directive
In your controller:
app.controller('ctrl', function ($scope,$timeout) {
$scope.requery = function (search) {
console.log(search);
}
});
In your view:
<div ng-app="app">
<div ng-controller="ctrl">
<delay-search delayed-method="requery(search)"></delay-search>
</div>
</div>
I know i'm late to the game but,hopefully this will help anyone still using 1.2.
Pre ng-model-options i found this worked for me, as ngchange will not fire when the value is invalid.
this is a slight variation on #doug's answer as it uses ngKeypress which doesn't care what state the model is in.
function delayChangeDirective($timeout) {
var directive = {
restrict: 'A',
priority: 10,
controller: delayChangeController,
controllerAs: "$ctrl",
scope: true,
compile: function compileHandler(element, attributes) {
var expression = attributes['ngKeypress'];
if (!expression)
return;
var ngModel = attributes['ngModel'];
if (ngModel) {
attributes['ngModel'] = '$parent.' + ngModel;
}
attributes['ngKeypress'] = '$$delay.execute()';
return {
post: postHandler,
};
function postHandler(scope, element, attributes) {
scope.$$delay = {
expression: expression,
delay: scope.$eval(attributes['ngKeypressDelay']),
execute: function () {
var state = scope.$$delay;
state.then = Date.now();
if (scope.promise) {
$timeout.cancel(scope.promise);
}
scope.promise = $timeout(function() {
delayedActionHandler(scope, state, expression);
scope.promise = null;
}, state.delay);
}
};
}
}
};
function delayedActionHandler(scope, state, expression) {
var now = Date.now();
if (now - state.then >= state.delay) {
scope.$parent.$eval(expression);
}
};
return directive;
};

Resources