Watcher not firing when contents of object changes - angularjs

Why is my $interval visibly refreshing the model?
I'm trying to automatically update the song that I'm playing right now and showing it on my website. For that, I used the $interval function. The problem is that the model (div) is refreshing every 10 seconds, while I want it only to refresh when the song changes (and just checking every 10 seconds)
I tried changing the $interval function with setInterval(), but no luck.
angular.module('lastfm-nowplaying', [])
.directive('lastfmnowplaying', ['uiCreation', 'lastFmAPI', 'lastFmParser', '$interval', function(uiCreation, lastFmAPI, lastFmParser, $interval){
var link = function(scope, element, attrs){
scope.$watch('config', function(value) {
load();
});
var load = function(){
function SongCheck(){
var latestTrack;
if (scope.config){
if (scope.config.apiKey){
lastFmAPI.getLatestScrobbles(scope.config)
.then(function(data){
latestTrack = lastFmParser.getLatestTrack(data);
angular.element(element).addClass('lastfm-nowplaying');
uiCreation.create(element[0], scope.config.containerClass, latestTrack);
}, function(reason) {
//Last.fm failure
});
}
else{
var latestTrack = {
title: scope.config.title,
artist: scope.config.artist,
largeImgUrl: scope.config.imgUrl,
xLargeImgUrl: scope.config.backgroundImgUrl,
}
angular.element(element).addClass('lastfm-nowplaying');
uiCreation.create(element[0], scope.config.containerClass, latestTrack);
}
}
}
SongCheck();
$interval(function () {
SongCheck();
} , 8000);
}
};
return {
scope:{
config: '=config'
},
link: link
};
}])
The code works, but I want the model to change when a change is detected (in this case the json file).

Watcher not firing when contents of object changes
Delete the $interval timer and use the "deep watch" version of the watcher:
scope.$watch('config', function(value) {
load(value);
̶}̶)̶;̶
}, true);
Normally the watcher only fires when the object reference changes. Set the second argument to true to have the watcher fire when the object contents changes.
For more information, see
AngularJS Developer Guide - Scope $watch depths

Related

Widget toggle functionality with $compile

I need to implement toggle functionality for the widget. When the user clicks on the minimization button then widget should shrink and expand when click on maximize button respectively.
I'm trying to achieve this functionality with below piece of code.
Functionality working as expected but it is registering the event multiple times(I'm emitting the event and catching in the filterTemplate directive).
How can we stop registering the event multiple times ?
Or
Is there anyway to like compiling once and on toggle button bind the template/directive to DOM and to make it work rest of the functionality .
So could you please help me to fix this.
function bindFilterTemplate(minimize) {
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) { // filter template is custom
// directive like this
// "<widget></widget>"
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope); // Compiling with
// current scope on every time when user click on
// the minimization button.
});
}
} else {
$timeout(function () {
element.find('.cls-filter-body').empty();
});
}
}
bindFilterTemplate();
// Directive
app.directive('widget', function () {
return {
restrict: 'E',
controller: 'widgetController',
link: function ($scope, elem) {
// Some code
}
};
});
// Controller
app.controller('widgetController', function ($scope) {
// This event emitting from parent directive
// On every compile, the event is registering with scope.
// So it is triggering multiple times.
$scope.$on('evt.filer', function ($evt) {
// Server call
});
});
I fixed this issue by creating new scope with $scope.$new().
When user minimizes the widget destroying the scope.
Please let me know if you have any other solution to fix this.
function bindFilterTemplate(minimize) {
// Creating the new scope.
$scope.newChildScope = $scope.$new();
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) {
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope.newChildScope);
});
}
} else {
$timeout(function () {
if ($scope.newChildScope) {
// Destroying the new scope
$scope.newChildScope.$destroy();
}
element.find('.cls-filter-body').empty();
});
}
}

Angular Directive not updating element using interval

i have this directive
angular.module('mydirectives').directive('slideShow', function ($interval) {
return{
scope:{slideShow:'=slideShow'},
link:function(scope, element, attrs){
element.css("background-size","cover");
element.css("background-repeat","none");
element.css("background-position","center center");
element.css("background-blend-mode","color");
element.css("background-color","rgba(0, 0, 0, 0.5)");
scope.index=0;
function nextSlide()
{
if(!scope.slideShow) return;
if(scope.slideShow.sources.length===0) return;
var url=scope.slideShow.sources[scope.index++];
if(scope.index>=scope.slideShow.sources.length) scope.index=0;
element.css({'background-image': 'url(' + url +')'});
}
nextSlide();
var interval= $interval(nextSlide,3000)
scope.$on("$destroy",function(){
$interval.cancel(interval);
})
}
}
});
this is how i apply it
<section class="primary" slide-show="slideShow">
now the controller which provides property "slideShow" gets the value via http request. when it comes back with response it sets the value of slideShow like this
$scope.slideShow={sources:["http:\\sources\someimage.jgp"]}
webApi.getHomePageModel().then(function(model){
$scope.model=model;
$scope.slideShow=model.slideShow;
},function(error){
console.dir(error);
});
The Problem: when this runs only the default value of slideshow works and element's background-image is set but after the response to http the new value is set to slideShow but the when interval function "nextSlide" executes then background-image is not updated. in debugger i can see the url values is being picked up correctly but element is not updated.
EDIT:I was making a stupid mistake, the updated model was not as expected the elements in sources were not strings as expected (they were being generated as complex objects rather than string value.) all working now. also no need for scope.$applyAsync because the $interval service handles that for you
If you are using setInterval then you need to manually rerun angular's digetst cycle:
function nextSlide()
{
if(!scope.slideShow) return;
if(scope.slideShow.sources.length===0) return;
var url=scope.slideShow.sources[scope.index++];
if(scope.index>=scope.slideShow.sources.length) scope.index=0;
element.css({'background-image': 'url(' + url +')'});
scope.applyAsync(); //this line!
//May not work in older angular versions, if such you should use scope.apply()
}
I got it working with this implementation
angular.module('app').directive('slideShow', function ($interval) {
return{
scope:{slideShow:'=slideShow'},
link:function(scope, element, attrs){
var index=0;
function nextSlide()
{
if(!scope.slideShow) return;
if(scope.slideShow.images.length===0) return;
var url=scope.slideShow.images[index++];
if(index>=scope.slideShow.images.length) index=0;
element.css({'background-image': 'url(' + url +')'});
}
var interval=false;
var watchSlideShow=scope.$watch("slideShow",function(){
if(!scope.slideShow) return;
if(scope.slideShow.images.length===0) return;
if(interval) return;
nextSlide();
var interval= $interval(nextSlide,5000);
});
scope.$on("$destroy",function(){
$interval.cancel(interval);
watchSlideShow();
});
}
}
});

calling a function when AngularUI Bootstrap modal has been dismissed and animation has finished executing

I'm using the Angular UI bootstrap modal and I ran into a bit of a problem.
I want to call a function when the bootstrap modal dismiss animation is finished. The code block below will call the cancel() function as soon as the modal starts to be dismissed - and NOT when the modal dismiss animation has finished.
Angular UI does not use events, so there is no 'hidden.bs.modal' event being fired (at least, not to my knowledge).
var instance = $modal.open({...});
instance.result.then(function(data) {
return success(data);
}, function() {
return cancel();
})
The cancel() block immediately runs when the modal starts to close. I need code to execute when the closing animation for the Bootstrap modal finishes.
How can I achieve this with angular UI?
Component for reference:
https://angular-ui.github.io/bootstrap/#/modal
Thanks!
A little late but hope it still helps! You can hijack the uib-modal-window directive and check when its scope gets destroyed (it is an isolated scope directive). The scope is destroyed when the modal is finally removed from the document. I would also use a service to encapsulate the functionality:
Service
app.service('Modals', function ($uibModal, $q) {
var service = this,
// Unique class prefix
WINDOW_CLASS_PREFIX = 'modal-window-interceptor-',
// Map to save created modal instances (key is unique class)
openedWindows = {};
this.open = function (options) {
// create unique class
var windowClass = _.uniqueId(WINDOW_CLASS_PREFIX);
// check if we already have a defined class
if (options.windowClass) {
options.windowClass += ' ' + windowClass;
} else {
options.windowClass = windowClass;
}
// create new modal instance
var instance = $uibModal.open(options);
// attach a new promise which will be resolved when the modal is removed
var removedDeferred = $q.defer();
instance.removed = removedDeferred.promise;
// remember instance in internal map
openedWindows[windowClass] = {
instance: instance,
removedDeferred: removedDeferred
};
return instance;
};
this.afterRemove = function (modalElement) {
// get the unique window class assigned to the modal
var windowClass = _.find(_.keys(openedWindows), function (windowClass) {
return modalElement.hasClass(windowClass);
});
// check if we have found a valid class
if (!windowClass || !openedWindows[windowClass]) {
return;
}
// get the deferred object, resolve and clean up
var removedDeferred = openedWindows[windowClass].removedDeferred;
removedDeferred.resolve();
delete openedWindows[windowClass];
};
return this;
});
Directive
app.directive('uibModalWindow', function (Modals) {
return {
link: function (scope, element) {
scope.$on('$destroy', function () {
Modals.afterRemove(element);
});
}
}
});
And use it in your controller as follows:
app.controller('MainCtrl', function ($scope, Modals) {
$scope.openModal = function () {
var instance = Modals.open({
template: '<div class="modal-body">Close Me</div>' +
'<div class="modal-footer"><a class="btn btn-default" ng-click="$close()">Close</a></div>'
});
instance.result.finally(function () {
alert('result');
});
instance.removed.then(function () {
alert('closed');
});
};
});
I also wrote a blog post about it here.

AngularJS directive: Updating the isolated scope

I want to create a directive which displays changing data. For simplicity let's say I want to display the current time. This means that once a second the directive needs to refresh itself.
In the docs there is an example for just this case (plunkr), but it procedurally updates the directive's content. I wonder if it could be done using data binding as well.
I imagine something like this:
module.directive('dateTime', function($interval) {
return {
scope: { // start with an empty isolated scope
},
template: '{{currentTime}}', // display time fetched from isolated scope
init: function(isolatedScope) {
$interval(function() {
isolatedScope.currentTime = new Date(); /* update isolated scope */
}, 1000);
},
destroy: function() { /* stop interval */ }
};
}
Is something like that possible? How?
Sure it's possible. The init() function you use in your code must be named link, and the destroy() function must be replaced by a listener on the $destroy event on the element passed to the link function:
module.directive('dateTime', function($interval) {
return {
scope: {
},
template: '{{currentTime}}',
link: function(isolatedScope, element) {
var interval = $interval(function() {
isolatedScope.currentTime = new Date(); /* update isolated scope */
}, 1000);
element.on('$destroy', function() {
$interval.cancel(interval);
});
}
};
}
Working example: http://plnkr.co/edit/rjwYZmR4qJ9Jn28d3jXR?p=preview

Access Element Style from Angular directive

I'm sure this is going to be a "dont do that!" but I am trying to display the style on an angular element.
<div ng-repeat="x in ['blue', 'green']" class="{{x}}">
<h3 insert-style>{{theStyle['background-color']}}</h3>
</div>
Result would be
<div class='blue'><h3>blue(psudeo code hex code)</h3></div>
<div class='green'><h3>green(psudeo code hex code)</h3></div>
I basically need to get the style attributes and display them.
Directive Code...
directives.insertStyle = [ function(){
return {
link: function(scope, element, attrs) {
scope.theStyle = window.getComputedStyle(element[0], null);
}
}
}];
Fiddle example: http://jsfiddle.net/ncapito/G33PE/
My final solution (using a single prop didn't work, but when I use the whole obj it works fine)...
Markup
<div insert-style class="box blue">
<h4 > {{ theStyle['color'] | toHex}} </h4>
</div>
Directive
directives.insertStyle = [ "$window", function($window){
return {
link: function(scope, element, attrs) {
var elementStyleMap = $window.getComputedStyle(element[0], null);
scope.theStyle = elementStyleMap
}
}
}];
Eureka!
http://jsfiddle.net/G33PE/5/
var leanwxApp = angular.module('LeanwxApp', [], function () {});
var controllers = {};
var directives = {};
directives.insertStyle = [ function(){
return {
link: function(scope, element, attrs) {
scope.theStyle = window.getComputedStyle(element[0].parentElement, null)
}
}
}];
leanwxApp.controller(controllers);
leanwxApp.directive(directives);
So that just took lots of persistence and guessing. Perhaps the timeout is unnecessary but while debugging it seemed I only got the style value from the parent after the timeout occurred.
Also I'm not sure why but I had to go up to the parentElement to get the style (even though it would realistically be inherited (shrug)?)
Updated fiddle again
Did one without the timeout but just looking at the parentElement for the style and it seems to still work, so scratch the suspicions about the style not being available at all, it's just not available where I would expect it.
Also holy cow there are a lot of ways to debug in Chrome:
https://developers.google.com/chrome-developer-tools/docs/javascript-debugging
I used
debugger;
statements in the code to drop in breakpoints without having to search all the fiddle files.
One more quick update
The code below comes out of Boostrap-UI from the AngularUI team and claims to provide a means to watch the appropriate events (haven't tried this but it looks like it should help).
http://angular-ui.github.io/bootstrap/
/**
* $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
* #param {DOMElement} element The DOMElement that will be animated.
* #param {string|object|function} trigger The thing that will cause the transition to start:
* - As a string, it represents the css class to be added to the element.
* - As an object, it represents a hash of style attributes to be applied to the element.
* - As a function, it represents a function to be called that will cause the transition to occur.
* #return {Promise} A promise that is resolved when the transition finishes.
*/
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
var $transition = function(element, trigger, options) {
options = options || {};
var deferred = $q.defer();
var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
var transitionEndHandler = function(event) {
$rootScope.$apply(function() {
element.unbind(endEventName, transitionEndHandler);
deferred.resolve(element);
});
};
if (endEventName) {
element.bind(endEventName, transitionEndHandler);
}
// Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
$timeout(function() {
if ( angular.isString(trigger) ) {
element.addClass(trigger);
} else if ( angular.isFunction(trigger) ) {
trigger(element);
} else if ( angular.isObject(trigger) ) {
element.css(trigger);
}
//If browser does not support transitions, instantly resolve
if ( !endEventName ) {
deferred.resolve(element);
}
});
// Add our custom cancel function to the promise that is returned
// We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
// i.e. it will therefore never raise a transitionEnd event for that transition
deferred.promise.cancel = function() {
if ( endEventName ) {
element.unbind(endEventName, transitionEndHandler);
}
deferred.reject('Transition cancelled');
};
return deferred.promise;
};
// Work out the name of the transitionEnd event
var transElement = document.createElement('trans');
var transitionEndEventNames = {
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd',
'transition': 'transitionend'
};
var animationEndEventNames = {
'WebkitTransition': 'webkitAnimationEnd',
'MozTransition': 'animationend',
'OTransition': 'oAnimationEnd',
'transition': 'animationend'
};
function findEndEventName(endEventNames) {
for (var name in endEventNames){
if (transElement.style[name] !== undefined) {
return endEventNames[name];
}
}
}
$transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
$transition.animationEndEventName = findEndEventName(animationEndEventNames);
return $transition;
}]);
The issue you'll face is that getComputedStyle is considered a very slow running method, so you will run into performance issues if using that, especially if you want angularjs to update the view whenever getComputedStyle changes.
Also, getComputedStyle will resolve every single style declaration possible, which i think will not be very useful. So i think a method to reduce the number of possible style is needed.
Definitely consider this an anti-pattern, but if you still insist in this foolishness:
module.directive('getStyleProperty', function($window){
return {
//Child scope so properties are not leaked to parent
scope : true,
link : function(scope, element, attr){
//A map of styles you are interested in
var styleProperties = ['text', 'border'];
scope.$watch(function(){
//A watch function to get the styles
//Since this runs every single time there is an angularjs loop, this would not be a very performant way to do this
var obj = {};
var computedStyle = $window.getComputedStyle(element[0]);
angular.forEach(styleProperties, function(value){
obj[value] = computedStyle.getPropertyValue(value);
});
return obj;
}, function(newValue){
scope.theStyle = newValue;
});
}
}
});
This solution works if you don't HAVE to have the directive on the child element. If you just place the declaration on the ng-repeat element itself, your solution works:
<div insert-style ng-repeat="x in ['blue', 'green']" class="{{x}}">
Fiddle

Resources