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);
})
}};
}
Related
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?
I have this angular controller :
var applicaton = angular.module("appUsed", ['ui.router','ngSanitize'] );
applicaton.controller('gamesController', ['$scope','$http','$sce','$stateParams',function(scope,http,sce,stateParams){
http.get('/'+stateParams.category+'/'+stateParams.id)
.success(function(result){
scope.Game = result.gameDetails;
scope.relatedGames = result.relatedGames;
console.log(scope.Game.title);
console.log(scope.Game.url);
scope.gameUrl = sce.trustAsHtml('<iframe allowfullscreen width="80%" height="600px src="'+scope.Game.url+'"></iframe>');
});
}]);
and this html :
<div class="game_and_description">
<div ng-bind-html="gameUrl"></div>
<h3> Description</h3>
<p> {{Game.description}}</p>
It shows me a white iframe. I searched over the internet and i've done everything right. The modules form angular ng-sanitize is running(called from <script> tag) and i have no error. the console log on scopes works like a charm. Don't know where should i look anymore. Please help.
You need to give a trust to the URL you are using in the iframe, and compile the html:
<div ng-controller="gamesController">
<div bind-html-compile="gameFrame"></div>
</div>
var myApp = angular
.module('appUsed',['ngSanitize'])
.controller('gamesController', ['$scope', '$sce', function (scope, sce) {
scope.Game = {
url: 'https://play.famobi.com/hop-dont-stop/A-DXC93'
};
scope.gameUrl = sce.trustAsResourceUrl(scope.Game.url);
scope.gameFrame = sce.trustAsHtml('<iframe allowfullscreen width="80%" height="600px" ng-src="{{gameUrl}}"></iframe>');
}])
.directive('bindHtmlCompile', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.$eval(attrs.bindHtmlCompile);
}, function (value) {
element.html(value && value.toString());
var compileScope = scope;
if (attrs.bindHtmlScope) {
compileScope = scope.$eval(attrs.bindHtmlScope);
}
$compile(element.contents())(compileScope);
});
}
};
}]);
See https://github.com/incuna/angular-bind-html-compile.
The working fiddle: http://jsfiddle.net/masa671/k2e43nvf/
I had a similar problem. I solved it like this :
my view :
<div ng-bind-html="getDescription()"></div>
my controller :
$scope.getDescription = function () {
if ($scope.description != null && $scope.todo.description.length > 0) {
return $sce.trustAsHtml($scope.description);
} else {
return 'no description.';
}
};
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.
I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch
I'm trying to set a variable, selected.child, on the $scope so that I can use it elsewhere. I'm still new to scopes in Angular, but not sure why I can't set something on the scope from within the directive. I can call scope functions.
I have a JSfiddle for it and code is posted below.
Thanks for the help in advance.
The HTML:
<div ng-controller="DashCtrl">
<h3>{{selected.child}}<h3>
<div ng-repeat="child in children" select={{child}}>
{{child.username}}
</div>
</div>
The javascript:
var dash = angular.module('dash', []);
dash.directive('select', function () {
return {
restrict: "A",
scope: false,
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.selected.child = jQuery.parseJSON(attrs.select); //Neither this
scope.setSelected(jQuery.parseJSON(attrs.select)); //Nor this is working
if (attrs.select != scope.selected) {
other_elements = angular.element(document.querySelectorAll('[select]'));
for (var i = 0; i < other_elements.length; i++) {
elm = jQuery(other_elements[i]);
elm.css('background', 'none');
}
element.css('background', '#F3E2A9');
}
});
}
};
});
dash.controller('DashCtrl', function ($scope) {
$scope.setSelected = function (child) {
$scope.selected.child = child;
};
$scope.children = [{
"age_group": "6-8",
"username": "my_child"
}, {
"age_group": "8-10",
"username": "another_child"
}];
$scope.selected = {
child: "none"
};
});
You are missing a call to $apply
Just modify your code as below
scope.selected.child = jQuery.parseJSON(attrs.select); //Neither this
//scope.setSelected(jQuery.parseJSON(attrs.select)); //Nor this is working
scope.$apply();