Directive in ng-repeat not re-processing after updating the array - angularjs

I have a directive which converts certain strings into links (/) tags. I use this directive inside an ng-repeat to add links to text within a list of s. However, I refresh this data from a server which overwrites the array of the ng-repeat. The list gets updated in the DOM but the text no longer has links in it as it did when processed by the directive. How do I make the directive reprocess the text to add links?
Relevant code below
Controller HTML
<div ng-repeat="post in posts track by $index" ng-if="!post.deleted">
<div class="post wrap" ng-click="openSinglePostView(post, $index)" >
<div class="post-desc" linkify="twitter" ng-bind="post.desc"></div>
</div>
</div>
Controller JS
$scope.posts = [];
function refresh(){
$http.get(Constants.GET_POSTS_URL, {params : paramObject})
.then(function (response){
$scope.posts = [];
for(var i = 0; i < response.data.resultsArray.length; i++){
var post = new PostFactory(response.data.resultsArray[i]);
$scope.posts.push(post);
}
});
}
refresh();
Directive code
angular.module('linkify').directive('linkify', ['$filter', '$timeout', 'linkify', function ($filter, $timeout, linkify) {
'use strict';
return {
//restrict: 'A',
link: function (scope, element, attrs) {
var type = attrs.linkify || 'normal';
$timeout(function () { element.html(linkify[type](element.html()));
});
}
};
}]);
and for reference, the directive uses these filters and factories
angular.module('linkify')
.filter('linkify', function (Constants) {
'use strict';
function linkify (_str, type) {
if (!_str) {
return;
}
var _text = _str.replace( /(?:https?\:\/\/|www\.)+(?![^\s]*?")([\w.,#?!^=%&:\/~+#-]*[\w#?!^=%&\/~+#-])?/ig, function(url) {
var wrap = document.createElement('div');
var anch = document.createElement('a');
anch.href = url;
anch.target = "_blank";
anch.innerHTML = url;
wrap.appendChild(anch);
return wrap.innerHTML;
});
// bugfix
if (!_text) {
return '';
}
// Twitter
if (type === 'twitter') {
_text = _text.replace(/(|\s)*#([\u00C0-\u1FFF\w]+)/g, '$1#$2');
_text = _text.replace(/(^|\s)*#([\u00C0-\u1FFF\w]+)/g, '$1#$2');
}
return _text;
}
//
return function (text, type) {
return linkify(text, type);
};
})
.factory('linkify', ['$filter', function ($filter) {
'use strict';
function _linkifyAsType (type) {
return function (str) {(type, str);
return $filter('linkify')(str, type);
};
}
return {
twitter: _linkifyAsType('twitter'),
icon: _linkifyAsType('icon'),
normal: _linkifyAsType()
};
}])

Could the track by $index be the problem? Have you tried without it?

Related

Angular directives collision

I would like to use 2 directives in the same app. The problem is that when I use the second one, the first one crashes with an ugly error: TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
The first directive is angular-fullpage.js (https://github.com/hellsan631/angular-fullpage.js) and the second one is angular bootstrap affix implementation (https://github.com/maxisam/angular-bootstrap-affix).
When I include both modules (directives), the fullpage directive crashes with the afformentioned error. If I remove the affix directive, then fullpage.js Works fine (just by removing the second directive from the modules).
How can I avoid directive collision? Are there any workarounds for this issue or should I just settle for just 1 of the directives?
Thanks!!!!
app.js:
var myApp = angular
.module(
'myApp',
[
'ngRoute',
'ngAnimate',
'ngMessages',
'ui.bootstrap',
'angular-loading-bar',
'LocalStorageModule',
'ngEnter',
'ng-Static-Include',
'ngResource',
'toastr',
'ng-Static-Include',
'pageslide-directive',
'ngRutChange',
'xmsbsStopPropagation',
'ngEnter',
'ng-rut',
'ngMessages',
'duScroll',
'dynamicNumber',
'xmsbsDirectives',
'salfaDirectives',
'mgcrea.bootstrap.affix',
'fullPage.js',
'ui.tinymce',
'mega-menu',
'bootstrap.fileField',
'ngTagsInput'
]);
Partial view (home) trying to use the fullpage directive and generating the error:
<div class="section">
<div ng-style="{'width': '100%', 'height': vm.divHeight+'px'}" style="margin-top:-7px;background:url(/content/images/03.jpg) center center; background-size:cover;">
<div class="col-sm-12">
<h1 class="fg-grayLight text-center text-shadow vert-align-center" style="z-index:2;" ng-style="{'padding-top':vm.divHeight/9+'px'}">sistema de recursos humanos 2.0</h1>
</div>
</div>
</div>
<div class="section">
<div class="col-sm-12">
<h2 class="text-center">noticias</h2>
</div>
</div>
Affix directive:
'use strict';
angular.module('mgcrea.bootstrap.affix', ['mgcrea.jquery'])
.directive('bsAffix', function($window, dimensions) {
var checkPosition = function(instance, el, options) {
var scrollTop = window.pageYOffset;
var scrollHeight = document.body.scrollHeight;
var position = dimensions.offset.call(el[0]);
var height = dimensions.height.call(el[0]);
var offsetTop = options.offsetTop * 1;
var offsetBottom = options.offsetBottom * 1;
var reset = 'affix affix-top affix-bottom';
var affix;
if(instance.unpin !== null && (scrollTop + instance.unpin <= position.top)) {
affix = false;
} else if(offsetBottom && (position.top + height >= scrollHeight - offsetBottom)) {
affix = 'bottom';
} else if(offsetTop && scrollTop <= offsetTop) {
affix = 'top';
} else {
affix = false;
}
if (instance.affixed === affix) return;
instance.affixed = affix;
instance.unpin = affix === 'bottom' ? position.top - scrollTop : null;
el.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''));
};
var checkCallbacks = function(scope, instance, iElement, iAttrs) {
if(instance.affixed) {
if(iAttrs.onUnaffix)
eval("scope." + iAttrs.onUnaffix);
}
else {
if(iAttrs.onAffix)
eval("scope." + iAttrs.onAffix);
}
};
return {
restrict: 'EAC',
link: function postLink(scope, iElement, iAttrs) {
var instance = {unpin: null};
angular.element($window).bind('scroll', function() {
checkPosition(instance, iElement, iAttrs);
checkCallbacks(scope, instance, iElement, iAttrs);
});
angular.element($window).bind('click', function() {
setTimeout(function() {
checkPosition(instance, iElement, iAttrs);
checkCallbacks(scope, instance, iElement, iAttrs);
}, 1);
});
}
};
});
fullpage directive (this directive requires the original jQuery fullpage lugin to work http://www.alvarotrigo.com/fullPage/):
(function () {
'use strict';
angular
.module('fullPage.js', [])
.directive('fullPage', fullPage);
fullPage.$inject = ['$timeout'];
function fullPage($timeout) {
var directive = {
restrict: 'A',
scope: { options: '=' },
link: link
};
return directive;
function link(scope, element) {
var pageIndex;
var slideIndex;
var afterRender;
var onLeave;
var onSlideLeave;
if (typeof scope.options === 'object') {
if (scope.options.afterRender) {
afterRender = scope.options.afterRender;
}
if (scope.options.onLeave) {
onLeave = scope.options.onLeave;
}
if (scope.options.onSlideLeave) {
onSlideLeave = scope.options.onSlideLeave;
}
} else if (typeof options === 'undefined') {
scope.options = {};
}
var rebuild = function () {
destroyFullPage();
$(element).fullpage(sanatizeOptions(scope.options));
if (typeof afterRender === 'function') {
afterRender();
}
};
var destroyFullPage = function () {
if ($.fn.fullpage.destroy) {
$.fn.fullpage.destroy('all');
}
};
var sanatizeOptions = function (options) {
options.afterRender = afterAngularRender;
options.onLeave = onAngularLeave;
options.onSlideLeave = onAngularSlideLeave;
function afterAngularRender() {
//We want to remove the HREF targets for navigation because they use hashbang
//They still work without the hash though, so its all good.
if (options && options.navigation) {
$('#fp-nav').find('a').removeAttr('href');
}
if (pageIndex) {
$timeout(function () {
$.fn.fullpage.silentMoveTo(pageIndex, slideIndex);
});
}
}
function onAngularLeave(page, next, direction) {
if (typeof onLeave === 'function' && onLeave(page, next, direction) === false) {
return false;
}
pageIndex = next;
}
function onAngularSlideLeave(anchorLink, page, slide, direction, next) {
if (typeof onSlideLeave === 'function' && onSlideLeave(anchorLink, page, slide, direction, next) === false) {
return false;
}
pageIndex = page;
slideIndex = next;
}
//if we are using a ui-router, we need to be able to handle anchor clicks without 'href="#thing"'
$(document).on('click', '[data-menuanchor]', function () {
$.fn.fullpage.moveTo($(this).attr('data-menuanchor'));
});
return options;
};
var watchNodes = function () {
return element[0].getElementsByTagName('*').length;
};
scope.$watch(watchNodes, rebuild);
scope.$watch('options', rebuild, true);
element.on('$destroy', destroyFullPage);
}
}
})();
Does the second depends on the first one? If does, are you compiling the directive one before embedding the second?
If you have 'collision' it's means that you're using some sort of 'use strict', would be more valuable if you show part of the code and see if transclude is part of the directive.

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);
})
}};
}

Decorating a directive by adding a function that will call the directive's controller

I use a directive that is declared like this :
(function (directives) {
var FilterDirective = (function () {
function FilterDirective() {
var directive = {};
directive.restrict = 'A';
directive.scope = true;
directive.controller = elasticui.controllers.FilterController;
directive.link = function (scope, element, attrs, filterCtrl) {
scope.$watch(element.attr('eui-filter') + " | euiCached", function (val) { return scope.filter.filter = val; });
var enabled = false;
var enabledAttr = element.attr('eui-enabled');
if (enabledAttr) {
scope.$watch(enabledAttr, function (val) { return scope.filter.enabled = val; });
enabled = scope.$eval(enabledAttr);
}
scope.filter = {
filter: scope.$eval(element.attr('eui-filter') + " | euiCached"),
enabled: enabled
};
filterCtrl.init();
};
return directive;
}
return FilterDirective;
})();
directives.FilterDirective = FilterDirective;
directives.directives.directive('euiFilter', FilterDirective);
})
The controller of the directive is :
(function (controllers) {
var FilterController = (function () {
function FilterController($scope) {
this.scope = $scope;
}
FilterController.prototype.init = function () {
var _this = this;
if (this.scope.filter.filter) {
var isEnabled = this.scope.filters.contains(this.scope.filter.filter);
if (!isEnabled && this.scope.filter.enabled) {
this.scope.filters.add(this.scope.filter.filter);
isEnabled = true;
}
}
this.scope.filter.enabled = isEnabled;
this.scope.$watch('filter.enabled', function (newVal, oldVal) {
if (newVal !== oldVal) {
_this.updateFilter();
}
});
this.scope.$watch('filter.filter', function (newVal, oldVal) {
if (!elasticui.util.EjsTool.equals(oldVal, newVal)) {
if (oldVal) {
_this.scope.filters.remove(oldVal);
}
_this.updateFilter();
}
});
};
FilterController.prototype.updateFilter = function () {
if (!this.scope.filter.filter) {
return;
}
if (this.scope.filter.enabled) {
this.scope.filters.add(this.scope.filter.filter);
}
else {
this.scope.filters.remove(this.scope.filter.filter);
}
};
FilterController.$inject = ['$scope'];
return FilterController;
})();
controllers.FilterController = FilterController;
})
Actually, the directive has a scope containing a filter object which contains two attributes filter : { enabled : boolean, filter : object} and the directive is used like this :
<label class="btn" ng-model="filter.enabled"
eui-filter="ejs.TermFilter('field','value')" btn-checkbox>
when the button is clicked the filter.enabled is set. My purpose is to add a behavior that will permit to change filter.enabled value via a function external to the directive.
The directive will look like this :
<label class="btn" ng-model="filter.enabled"
eui-filter="ejs.TermFilter('field','value')" eui-enable-fn="fn(somevariable)" btn-checkbox>
where fn will take the somevariable and set it to the filter.enabled.
Thanks in advance,
If you want to enable/disable a filter through the pressure of a button why not declare a filter with the property eui-enabled set to a custom toggling variable?
In other words it would result as:
HTML:
<label class="btn" eui-filter="..." eui-enabled="my_toggling_variable">
<button type=button ng-click="toggleVar()"></button>
JS:
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.my_toggling_variable = false;
$scope. toggleVar = function(){
$scope.my_toggling_variable = !$scope.my_toggling_variable;
};
}]);
Hope to have understood well the topic.

AngularJS copy to clipboard

Is there a way to make a copy button with a copy function that will copy all the contents of a modal and you can paste it to notepad
I needed this functionality in my Controller, as the text to be copied is dynamic, here's my simple function based on the code in the ngClipboard module:
function share() {
var text_to_share = "hello world";
// create temp element
var copyElement = document.createElement("span");
copyElement.appendChild(document.createTextNode(text_to_share));
copyElement.id = 'tempCopyToClipboard';
angular.element(document.body.append(copyElement));
// select the text
var range = document.createRange();
range.selectNode(copyElement);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
// copy & cleanup
document.execCommand('copy');
window.getSelection().removeAllRanges();
copyElement.remove();
}
P.S.
You're welcome to add a comment now telling me how bad it is to manipulate DOM from a Controller.
If you have jquery support use this directive
.directive('copyToClipboard', function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
elem.click(function () {
if (attrs.copyToClipboard) {
var $temp_input = $("<input>");
$("body").append($temp_input);
$temp_input.val(attrs.copyToClipboard).select();
document.execCommand("copy");
$temp_input.remove();
}
});
}
};
});
Html
Copy text
if you don't want to add a new library to your project, and you create it by your self, here is a simple, easy solution:
note: I created it with promise functionality (which is awesome)
here is CopyToClipboard.js module file
angular.module('CopyToClipboard', [])
.controller('CopyToClipboardController', function () {
})
.provider('$copyToClipboard', [function () {
this.$get = ['$q', '$window', function ($q, $window) {
var body = angular.element($window.document.body);
var textarea = angular.element('<textarea/>');
textarea.css({
position: 'fixed',
opacity: '0'
});
return {
copy: function (stringToCopy) {
var deferred = $q.defer();
deferred.notify("copying the text to clipboard");
textarea.val(stringToCopy);
body.append(textarea);
textarea[0].select();
try {
var successful = $window.document.execCommand('copy');
if (!successful) throw successful;
deferred.resolve(successful);
} catch (err) {
deferred.reject(err);
//window.prompt("Copy to clipboard: Ctrl+C, Enter", toCopy);
} finally {
textarea.remove();
}
return deferred.promise;
}
};
}];
}]);
that's it, thanks to https://gist.github.com/JustMaier/6ef7788709d675bd8230
now let's use it
angular.module('somthing')
.controller('somthingController', function ($scope, $copyToClipboard) {
// you are free to do what you want
$scope.copyHrefToClipboard = function() {
$copyToClipboard.copy(/*string to be coppied*/$scope.shareLinkInfo.url).then(function () {
//show some notification
});
};
}
and finally the HTML
<i class="fa fa-clipboard" data-ng-click="copyHrefToClipboard($event)"></i>
hope this saves your time
You can use a module I made, ngClipboard. Here's the link https://github.com/nico-val/ngClipboard
You can use either ng-copyable directive, or the ngClipboard.toClipboard() factory.
In HTML:
</img>
In Controller:
$scope.copyToClipboard = function (name) {
var copyElement = document.createElement("textarea");
copyElement.style.position = 'fixed';
copyElement.style.opacity = '0';
copyElement.textContent = decodeURI(name);
var body = document.getElementsByTagName('body')[0];
body.appendChild(copyElement);
copyElement.select();
document.execCommand('copy');
body.removeChild(copyElement);
}
document.execCommand is now deprecated. Instead you can do:
HTML:
<i class="fa fa-copy" ng-click="copyToClipboard('some text to copy')"></i>
Controller:
$scope.copyToClipboard = function(string) {
navigator.clipboard.writeText(string)
.then(console.log('copied!'));
}
try this:
app.service('ngCopy', ['$window', function ($window) {
var body = angular.element($window.document.body);
var textarea = angular.element('<textarea/>');
textarea.css({
position: 'fixed',
opacity: '0'
});
return function (toCopy) {
textarea.val(toCopy);
body.append(textarea);
textarea[0].select();
try {
var successful = document.execCommand('copy');
if (!successful)
throw successful;
} catch (err) {
window.prompt("Copy to clipboard: Ctrl+C, Enter", toCopy);
}
textarea.remove();
}
}]);
You need to call this service to your controller. You can do like this:
controllers.MyController = ['$scope', 'ngCopy',
function ($scope, ngCopy) {
ngCopy(copyText);
}];

AngularJS Passing Variable to Directive

I'm new to angularjs and am writing my first directive. I've got half the way there but am struggling figuring out how to pass some variables to a directive.
My directive:
app.directive('chart', function () {
return{
restrict: 'E',
link: function (scope, elem, attrs) {
var chart = null;
var opts = {};
alert(scope[attrs.chartoptions]);
var data = scope[attrs.ngModel];
scope.$watch(attrs.ngModel, function (v) {
if (!chart) {
chart = $.plot(elem, v, opts);
elem.show();
} else {
chart.setData(v);
chart.setupGrid();
chart.draw();
}
});
}
};
});
My controller:
function AdListCtrl($scope, $http, $rootScope, $compile, $routeParams, AlertboxAPI) {
//grabing ad stats
$http.get("/ads/stats/").success(function (data) {
$scope.exports = data.ads;
if ($scope.exports > 0) {
$scope.show_export = true;
} else {
$scope.show_export = false;
}
//loop over the data
var chart_data = []
var chart_data_ticks = []
for (var i = 0; i < data.recent_ads.length; i++) {
chart_data.push([0, data.recent_ads[i].ads]);
chart_data_ticks.push(data.recent_ads[i].start);
}
//setup the chart
$scope.data = [{data: chart_data,lines: {show: true, fill: true}}];
$scope.chart_options = {xaxis: {ticks: [chart_data_ticks]}};
});
}
My Html:
<div class='row-fluid' ng-controller="AdListCtrl">
<div class='span12' style='height:400px;'>
<chart ng-model='data' style='width:400px;height:300px;display:none;' chartoptions="chart_options"></chart>
{[{ chart_options }]}
</div>
</div>
I can access the $scope.data in the directive, but I can't seem to access the $scope.chart_options data.. It's definelty being set as If I echo it, it displays on the page..
Any ideas what I'm doing wrong?
UPDATE:
For some reason, with this directive, if I move the alert(scope[attrs.chartoptions]); to inside the $watch, it first alerts as "undefined", then again as the proper value, otherwise it's always undefined. Could it be related to the jquery flot library I'm using to draw the chart?
Cheers,
Ben
One problem I see is here:
scope.$watch(attrs.ngModel, function (v) {
The docs on this method are unfortunately not that clear, but the first argument to $watch, the watchExpression, needs to be an angular expression string or a function. So in your case, I believe that you need to change it to:
scope.$watch("attrs.ngModel", function (v) {
If that doesn't work, just post a jsfiddle or jsbin.com with your example.

Resources