How to `$destroy` directive and replace with new values - angularjs

I have 2 set of arrays. the first set is being as the default. when user click the next button, i need to update the new set. I can do this.
But the previous set as well exist. I don't know how to properly remove that, and define the new values. I am looking for the way that, which remove the event listner while remove the DOM. and memory leak should not be as well.
here is my js :
var myApp = angular.module('myApp', []);
myApp.controller('main', function ($scope) {
$scope.values = [{"name":"one", "num" : 1}, {"name":"two", "num" : 2}, {"name":"three", "num" : 3}];
$scope.next = function () {
$scope.index = 4;
$scope.values = [{"name":"four", "num" : 4}, {"name":"five", "num" : 5}, {"name":"six", "num" : 6}];
}
$scope.index = 0;
$scope.update = function (value) {
console.log("clicked " + value.num);
$scope.index = value.num;
$scope.$apply();
}
});
myApp.directive("newArray", function ($compile) {
return {
scope : {
value : "=",
index : "=",
update:"&"
},
link : function (scope, element, attrs) {
var getTemplate = function (value, index) {
switch(index) {
case 0 :
return '<div ng-click="update()">I am here {{index}} {{value.name}}</div>'
break;
case 1 :
return $('<div />', {
class:'blue',
html : "<h1>testing{{index}} {{value.name}}</h1>",
click : function () {
scope.update({num: scope.value.num});
}
});
break;
case 2 :
return $('<div />', {
class:'green',
html : "<h1>testing{{index}} {{value.name}}</h1>",
click : function () {
scope.update({num: scope.value.num});
}
});
break;
}
}
element.html(getTemplate(scope.value, scope.index));
$compile(element.contents())(scope);
element.replaceWith(element.contents());
}
}
});
Live Demo
updating my plnkr is appreciated. may help my future reference.

For such a different requirement I'll not go for the isolated scope directive that will mess up, As you want to use replace directive DOM with the the directive template. Another reason you are using ng-repeat on your directive which is not maintaining DOM structure in proper manner as you are replacing directive DOM with the newly constructed DOM. Instead of which I created a simple directive that do loop inside directive and create a element with new isolated scope & appending it to Pseudo element inside watcher.
Markup
<body ng-controller="main">
<a ng-click="next()" href="#">Next</a>
<h1>{{index}}</h1>
<new-array values='values'></new-array>
</body>
Directive
myApp.directive("newArray", function ($compile) {
return {
link : function (scope, element, attrs) {
var getTemplate = function (value, index) {
var newScope = scope.$new(true);
newScope.value = value;
switch(index) {
case 0 :
newScope.index = index;
return $compile('<div ng-click="$parent.update(value)">I am here {{value.num}} {{value.name}}</div>')(newScope)
break;
case 1 :
return $compile($('<div />', {
class:'blue',
html : "<h1>testing{{index}} {{value.name}}</h1>",
click : function () {
scope.update({num: scope.values[index].num});
}
}))(newScope);
break;
case 2 :
return $compile($('<div />', {
class:'green',
html : "<h1>testing{{index}} {{value.name}}</h1>",
click : function () {
scope.update({num: scope.values[index].num});
}
}))(newScope);
break;
}
}
scope.$watch('values', function(newVal){
var html = '', dom = $('<div/>');
element.empty();
for(var i=0;i < newVal.length; i++){
element.append(getTemplate(newVal[i], i))
}
//element.replaceWith(dom)
}, true);
}
}
})
Working Plunkr
Note
You can not really think of $destroy in your case. It just like
destructor which used to clear the events.

Related

AngularJS :wait in 'link' of directive is not working

In the below source, the watch method in the link part of a custom directive is not working. I use 'link' within the directive because I have to update the DOM structure.
How can I get the watch in the link{} of the directive working EACH time the button is pushed?
EDIT: I found the wrong code. See below 'ERROR' and 'CORRECT' code.
The HTML above this script is (click on a button to increment a variable):
<div ng-controller="AppController as vmx">
<button ng-click="vmx.incrementFoo()">Increment Foo</button>:
{{ vmx.fooCount }}.
<div foo-count-updated></div>
</div>
Angular code:
angular.module( "myapp", [])
.controller( "AppController", myAppController)
.directive('showAlsoInCustomDirective', showAlsoInCustomDirective);
// *** CONTROLLER
function myAppController( $scope ) {
var vm = this;
vm.fooCount = 0;
vm.copiedFooCount = 0;
// ERROR code:
// **vm.getFooCount** = function() {
// return vm.fooCount;
// }
// CORRECT code:
getFooCount = function() {
return vm.fooCount;
}
vm.getFooCount = getFooCount;
vm.incrementFoo = incrementFoo;
function incrementFoo() {
++vm.fooCount;
}
}
// *** DIRECTIVE
.directive('fooCountUpdated', fooCountUpdater);
function fooCountUpdater() {
var indirectivecounter = 0;
getFooCountInDirective = function() {
return getFooCount();
}
var watcherFn = function (watchScope) {
return getFooCountInDirective();
}
return {
link: function (scope, element, attrs) {
scope.$watch(watcherFn, function (newValue, oldValue) {
element.html( "Got the change: " + newValue);
})
}};
}
The complete source is put in this file:
https://plnkr.co/edit/J6nfLQ3dmLW0gDNXV0J5?p=preview
As indicated above, the solution was simple. It is also in the plunker file.
HTML:
<div ng-controller="AppController as vmx">
<button ng-click="vmx.incrementFoo()">Increment Foo</button>:
{{ vmx.fooCount }}.
<div foo-count-updated></div>
</div>
Angular code:
angular.module( "myapp", [])
.controller( "AppController", function( $scope ) {
var vm = this;
vm.fooCount = 0;
getFooCount = function() {
return vm.fooCount;
}
vm.getFooCount = getFooCount;
vm.incrementFoo = incrementFoo;
function incrementFoo() {
++vm.fooCount;
}
})
.directive('fooCountUpdated', fooCountUpdater);
function fooCountUpdater() {
var indirectivecounter = 0;
getFooCountInDirective = function() {
return getFooCount();
}
var watcherFn = function (watchScope) {
return getFooCountInDirective();
}
return {
link: function (scope, element, attrs) {
scope.$watch(watcherFn, function (newValue, oldValue) {
element.html( "Got the change: " + newValue);
})
}};
}

Access controller function from directive to update an HashMap

I was trying to call a controller function from a directive in order to update a counter inside an hash-map.
After reading a few solutions, I ended up doing this:
'use strict';
var dragDropApp = angular.module('dragDropApp', []);
dragDropApp.controller('DragDropCtrl', function($scope) {
$scope.itemCount = {
'item1' : {
'count' : 0
},
'item2' : {
'count' : 0
},
'item3' : {
'count' : 0
}
};
//this.updateItemCounter = function(itemId) {
// $scope.itemCount[itemId].count++;
//}
$scope.updateItemCounter = function(itemId) {
$scope.itemCount[itemId].count++;
}
}
dragDropApp.directive('droppable', function() {
return {
restrict : 'A',
scope : {
drop : '&', // parent
bin : '=' // bi-directional scope
},
controller : 'DragDropCtrl',
link : function(scope, element, attrs, DragDropCtrl) {
var el = element[0];
el.addEventListener('drop', function(e) {
var item = document.getElementById(
e.dataTransfer.getData('Text')).cloneNode(true);
//DragDropCtrl.updateItemCounter(item.id);
>>>> scope.$parent.updateItemCounter(item.id); <<<<
return false;
}, false);
}
}
});
It works and does what I want, but I don't know if this approach is correct. Is it?
I've also tried to use the controller to access the function updateItemCounter, but the hash-map does not get updated, having the same values every time I call the function.
In html you should set attributes like this:
<div droppable bin="something" drop="updateItemCounter(itemId)"></div>
In directive if you want to call your controller's function you should call it like this. Note that property name of passed object must be the same as used in html
link : function(scope, element, attrs, DragDropCtrl) {
...
scope.updateItemCounter({itemId: item.id});
...
}
if you wold like to pass more arguments you should use in html:
<div droppable drop="updateItemCounter(itemId, param2, param3)"></div>
and call:
scope.updateItemCounter({itemId: item.id, param2: 'something', param3: 'something'});
Read these answers:
https://stackoverflow.com/a/27997722/1937797
https://stackoverflow.com/a/23843218/1937797

Unable to update the `$scope` index value from DOM click

In my directive, i have custom templates and replacing with existing one. when the custom templates click, i need to update the $scope.index to update.
but it's not working. but when i console the property it all works. what is the correct way to do this?
here is my custom directive:
var myApp = angular.module('myApp', []);
myApp.controller('main', function ($scope) {
$scope.values = [{"name":"one", "num" : 1}, {"name":"two", "num" : 2}, {"name":"three", "num" : 3}]
$scope.index = 0;
$scope.update = function (num) {
$scope.index = num;
}
});
myApp.directive("newArray", function ($compile) {
return {
scope : {
value : "=",
index : "=",
update:"&"
},
link : function (scope, element, attrs) {
var getTemplate = function (val, index) {
switch(index) {
case 0 :
return $('<div />', {
class:'red',
html : "<h1>testing</h1>",
click : function () {
console.log(scope.value.num); //works
scope.update(scope.value.num); //not wroking
}
});
break;
case 1 :
return $('<div />', {
class:'blue',
html : "<h1>testing</h1>",
click : function () {
scope.update(scope.value.num);
}
});
break;
case 2 :
return $('<div />', {
class:'green',
html : "<h1>testing</h1>",
click : function () {
scope.update(scope.value.num);
}
});
break;
}
}
element.html(getTemplate(scope.value, scope.index));
$compile(element.contents())(scope);
element.replaceWith(element.contents());
}
}
})
Live Demo
In the html change the line
<new-array index='$index' update="update" value='value' ng-repeat="value in values">{{value.name}}</new-array>
to
<new-array index='$index' update="update($index)" value='value' ng-repeat="value in values">{{value.name}}</new-array>
In the js change:
scope.update(scope.value.num);
to
scope.update({num: scope.value.num});
and finally change:
$scope.update = function (num) {
$scope.index = num;
}
to
$scope.update = function (num) {
$scope.index = num;
$scope.$apply();
}
See updated plunker

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 - 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
]

Resources