Angular bootstrap carousel digest - angularjs

How can I prevent the carousel firing a full digest cycle and re-running my collection filter each time it slides to a new image.
The plunker below shows shows what I mean if you click an item and watch the log. http://plnkr.co/edit/X062Xr90G807uqURqts9?
<carousel disable-ng-animate ng-click="$event.stopPropagation();" interval="5000">
<slide ng-repeat="photo in object.photos" active="photo.active">
<img ng-src="{{photo.getUrl({'maxWidth': 350, 'maxHeight': 250})}}" style="margin:auto;">
</slide>
</carousel>

If your collection will not change, you can use one-time-binding:
<div ng-repeat="item in ::collection | example" ng-click="setSelected(item)">
Here is the updated plunker
But if it's not good for you, you must get into the carousel directive, and look if you see $apply.
$apply will cause to $rootScope.$digestand because of this the filter will fire for any change.
EDIT
After looking on carousel.html (the directive template)
you can see ng-mouseenter="pause()" ng-mouseleave="play()".
This is a build-in angular directive and behind the scenes angular use $apply, so I can't see any way to avoid a full-digest on carousel directive.
Here is the angular code:
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(eventName) {
var directiveName = directiveNormalize('ng-' + eventName);
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
return {
restrict: 'A',
compile: function($element, attr) {
// We expose the powerful $event object on the scope that provides access to the Window,
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
// checks at the cost of speed since event handler expressions are not executed as
// frequently as regular change detection.
var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event:event});
};
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
scope.$evalAsync(callback);
} else {
scope.$apply(callback);
}
});
};
}
};
}];
}
);

Related

AngularJS scroll directive - How to prevent re-rendering whole scope

I have an AngularJS Application with a scroll directive implemented as the following:
http://jsfiddle.net/un6r4wts/
app = angular.module('myApp', []);
app.run(function ($rootScope) {
$rootScope.var1 = 'Var1';
$rootScope.var2 = function () { return Math.random(); };
});
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.scrolled = true;
} else {
scope.scrolled = false;
}
scope.$apply();
});
};
});
The HTML looks the following:
<div ng-app="myApp" scroll ng-class="{scrolled:scrolled}">
<header></header>
<section>
<div class="vars">
{{var1}}<br/><br/>
{{var2()}}
</div>
</section>
</div>
I only want the class scrolled to be added to the div once the page is scrolled more than 100px. Which is working just fine, but I only want that to happen! I don't want the whole scope to be re-rendered. So the function var2() should not be executed while scrolling. Unfortunately it is though.
Is there any way to have angular only execute the function which is bound to the window element without re-rendering the whole scope, or am I misunderstanding here something fundamentally to AngularJS?
See this fiddle:
http://jsfiddle.net/un6r4wts/
Edit:
This seems to be a topic about a similar problem:
Angularjs scope.$apply in directive's on scroll listener
If you want to calculate an expression only once, you can prefix it with '::', which does exactly that. See it in docs under One-time binding:
https://docs.angularjs.org/guide/expression
Note, this requires angular 1.3+.
The reason that the expressions are calculated is because when you change a property value on your scope, then dirty check starts and evaluates all the watches for dirty check. When the view uses {{ }} on some scope variable, it creates a binding (which comes along with a watch).

JQuery UI Spinner is not updating ng-model in angular

Angular's ng-model is not updating when using jquery-ui spinner.
Here is the jsfiddle http://jsfiddle.net/gCzg7/1/
<div ng-app>
<div ng-controller="SpinnerCtrl">
<input type="text" id="spinner" ng-model="spinner"/><br/>
Value: {{spinner}}
</div>
</div>
<script>
$('#spinner').spinner({});
</script>
If you update the text box by typing it works fine (you can see the text change). But if you use the up or down arrows the model does not change.
Late answer, but... there's a very simple and clean "Angular way" to make sure that the spinner's spin events handle the update against ngModel without resorting to $apply (and especially without resorting to $parse or an emulation thereof).
All you need to do is define a very small directive with two traits:
The directive is placed as an attribute on the input element you want to turn into a spinner; and
The directive configures the spinner such that the spin event listener calls the ngModel controller's $setViewValue method with the spin event value.
Here's the directive in all its clear, tiny glory:
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
Note that $setViewValue is intended for exactly this situation:
This method should be called when an input directive wants to change
the view value; typically, this is done from within a DOM event
handler.
Here's a link to a working demo.
If the demo link provided above dies for some reason, here's the full example script:
(function () {
'use strict';
angular.module('ExampleApp', [])
.controller('ExampleController', ExampleController)
.directive('jqSpinner', jqSpinner);
function ExampleController() {
var c = this;
c.exampleValue = 123;
};
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
})();
And the minimal example template:
<div ng-app="ExampleApp" ng-controller="ExampleController as c">
<input jq-spinner ng-model="c.exampleValue" />
<p>{{c.exampleValue}}</p>
</div>
Your fiddle is showing something else.
Besides this: Angular can not know about any changes that occur from outside its scope without being aknowledged.
If you change a variable of the angular-scope from OUTSIDE angular, you need to call the apply()-Method to make Angular recognize those changes. Despite that implementing a spinner can be easily achieved with angular itself, in your case you must:
1. Move the spinner inside the SpinnerCtrl
2. Add the following to the SpinnerCtrl:
$('#spinner').spinner({
change: function( event, ui ) {
$scope.apply();
}
}
If you really need or want the jQuery-Plugin, then its probably best to not even have it in the controller itself, but put it inside a directive, since all DOM-Manipulation is ment to happen within directives in angular. But this is something that the AngularJS-Tutorials will also tell you.
Charminbear is right about needing $scope.$apply(). Their were several problems with this approach however. The 'change' event only fires when the spinner's focus is removed. So you have to click the spinner then click somewhere else. The 'spin' event is fired on each click. In addition, the model needs to be updated before $scope.$apply() is called.
Here is a working jsfiddle http://jsfiddle.net/3PVdE/
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
var mdlAttr = $(this).attr('ng-model').split(".");
if (mdlAttr.length > 1) {
var objAttr = mdlAttr[mdlAttr.length - 1];
var s = $scope[mdlAttr[0]];
for (var i = 0; i < mdlAttr.length - 2; i++) {
s = s[mdlAttr[i]];
}
s[objAttr] = ui.value;
} else {
$scope[mdlAttr[0]] = ui.value;
}
$scope.$apply();
}
}, 0);
});
Here's a similar question and approach https://stackoverflow.com/a/12167566/584761
as #Charminbear said angular is not aware of the change.
However the problem is not angular is not aware of a change to the model rather that it is not aware to the change of the input.
here is a directive that fixes that:
directives.directive('numeric', function() {
return function(scope, element, attrs) {
$(element).spinner({
change: function(event, ui) {
$(element).change();
}
});
};
});
by running $(element).change() you inform angular that the input has changed and then angular updates the model and rebinds.
note change runs on blur of the input this might not be what you want.
I know I'm late to the party, but I do it by updating the model with the ui.value in the spin event. Here's the updated fiddle.
function SpinnerCtrl($scope, $timeout) {
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
$scope.spinner = ui.value;
$scope.$apply();
}
}, 0);
});
}
If this method is "wrong", any suggestions would be appreciated.
Here is a solution that updates the model like coder’s solution, but it uses $parse instead of parsing the ng-model parameter itself.
app.directive('spinner', function($parse) {
return function(scope, element, attrs) {
$(element).spinner({
spin: function(event, ui) {
setTimeout(function() {
scope.$apply(function() {
scope._spinnerVal = = element.val();
$parse(attrs.ngModel + "=_spinnerVal")(scope);
delete scope._spinnerVal;
});
}, 0);
}
});
};
});

AngularJS event for when model binding or ng-repeat is complete?

We have a large model and it takes a couple seconds for ng-repeat to bind all the items in the model to the form. We would like to show a spinner while it this is happening. Is there some event that fires when binding is complete so we know when to hide the spinner?
Plunkr: http://plnkr.co/edit/GzzTW4?p=preview
Use ng-show on the spinner If you are using 1.2 use ng-if
<div ng-controller="Ctrl">
<div ng-show="complete">Complete={{complete}}</div>
<div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
thing {{thing}}
</div>
</div>
In your directive use $last to determine if rendering is done and then change the variable that you have the ng-show/ngif defined on.
function Ctrl($scope) {
$scope.complete=false;
$scope.doComplete = function() {
$scope.complete = true;
}
$scope.things = [
'A', 'B', 'C'
];
}
angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
return function(scope, element, attrs) {
if (scope.$last) {
scope.$eval('doComplete()');
}
};
});
You can watch for $last item compile/link function, and fire a custom event to the scope
In that kind of situations, I use the $timeout service mixed with the $viewContentLoaded event fired by angular ui router (if you use ui router) :
about $timeout :
This service is just a simple decorator for $timeout service that adds a "flush" and "verifyNoPendingTasks" methods.
about $viewContentLoaded
fired once the view is loaded, after the DOM is rendered. The '$scope' of the view emits the event.
My personal usecase is for a paymentForm to dynamically generate its hidden inputs (using HTML data computed serverside that I insert through ng-bind-html) and submit to the payment Gateway :
$scope.$on('$viewContentLoaded', function() {
$timeout(function () {
$scope.paymentForm.submit();
});
});
FYI in the above code example, .submit() is a function from a custom directive used with the form in order to be able to autosubmit the form.
Julien
For this I normally create a spinner div in your view with an ng-show="submitting". Then when the data is loaded, you set the $scope.submitting to 'false' show the spinner is hidden.
<!-- In your HTML -->
<div class="spinner" ng-show="submitting">
<div ng-repeat="p in people">
{{p.name}}
</div>
//In Javascript
$scope.submitting = true;
$scope.load_data = function(){
$http.get('/api/route')
.then(function(success){
$scope.submitting = false;
},function(error){
console.log(error);
});
}
I hope that helps

AngularJS (1.1.5): Can you cancel Directives with priority cancel

Is it possible to keep the built-in ng-click handler from firing when you have a custom ng-click directive with a priority > 0 that fires first? Or to delay the built-in one in some way?
In my case I have a custom ngClick directive that applies an animation to an element then waits for the animation to complete. Once it is complete, and only then, should the built-in ngClick fire. Reasoning is that the clicked element is on a slide-out drawer that is automatically hidden from within the ngClick handler. If the directive can't keep it from firing, then the drawer is closed before the animation even starts.
From the custom directive, I can use this to invoke the default ngClick, but the original needs to be cancelled in this case...
Requirement: The solution should not require the developer write any code within their ngClick handler. I am definitely able to code this in the controller, but want to avoid having the controller having to know that it is supposed to wait. (i.e. in case I change how the directive implements the indicator and it takes a different amount of time)
$timeout(function() {service.close();}, 400);
Here is an example of what I want to accomplish.
Markup
<li ng-repeat="product in service.products"
ng-click="onClick('{{product}}')"
pre-click="onClicking('{{product}}')"
animate="wasClicked(product)">
{{product}}
</li>
Directive
angular.module('sales.directives')
.directive('ngClick', [
'$timeout',
function ($timeout) {
return {
priority: 50, // higher priority
restrict: 'A',
scope: false,
link: function (scope, element, attributes) {
element.bind('click', function () {
if (attributes.preClick) {
eval('scope.' + attributes.preClick);
}
});
if (attributes.animate !== undefined) {
scope.$watch(attributes.animate, function (newValue) {
if (newValue == true) {
element.addClass('animated');
// pause 400ms so animation can complete
$timeout(angular.noop, 400)
.then(function () {
element.removeClass('animated');
// I would like to invoke the original
// ngClick here, and then remove it from the
// queue so that it doesn't fire it again.
// Reason for invoking it here is that if I
// don't, then the base ngClick event will
// fire before this promise is resolved.
eval('scope.' + element.ngClick);
// ??
});
}
});
}
}
};
}]);
Controller
angular.module('sales')
.controller(
'ProductController',
[
'$scope', 'ProductService',
function ($scope, $timeout, service) {
$scope.clickedProduct = null;
$scope.onClicking = function (product) {
$scope.clickedProduct = product;
};
$scope.wasClicked = function (product) {
return $scope.clickedProduct === product;
};
$scope.onClick = function (product) {
service.selected = product;
};
}
]);
Any thoughts? Is there a better way to do this?

Creating a scope-independent fadein/fadeout directive in AngularJS

To set the stage - this is not happening within a single scope, where I can bind a simple attribute. The element I want to fade in/out does not sit inside a controller, it sits inside the ng-app (rootScope). Further, the button that's clicked is in a child scope about 3 children deep from root.
Here is how I'm currently solving this:
HTML (sitting in root scope):
<ul class="nav-secondary actions"
darthFader fadeDuration="200"
fadeEvent="darthFader:secondaryNav">
Where darthFader is my directive.
Directive:
directive('darthFader',
function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
$scope.$on(attrs.fadeevent, function(event,options) {
$(element)["fade" + options.fade || "In"](attrs.fadeduration || 200);
});
}
}
})
So here I'm creating an event handler, specific to a given element, that is calling fadeIn or fadeOut, depending on an option being passed through the event bus (or defaulting to fadeIn/200ms).
I am then broadcasting an event from $rootScope to trigger this event:
$rootScope.$broadcast('darthFader:secondaryNav', { fade: "Out"});
While this works, I'm not crazy about creating an event listener for every instance of this directive (while I don't anticipate having too many darthFader's on a screen, it's more for the pattern I would establish). I'm also not crazy about coupling my attribute in my view with an event handler in both my controller & directive, but I don't currently have a controller wrapping the secondary-nav, so I'd have to bind the secondaryNav to $rootScope, which I don't love either. So my questions:
Is there a way to do this without creating an event handler every time I instantiate my directive? (maybe a service to store a stateful list of elements?)
How should I decouple my view, controller & directive?
Any other obvious questions I'm missing?
Cheers!
You mention in your question
The element I want to fade in/out does not sit inside a controller, it sits inside the ng-app (rootScope).
I believe if I were to write this same functionality, I would put the element in its own controller--controllers are responsible for managing the intersection of the view and the model, which is exactly what you're trying to do.
myApp.controller('NavController', function($scope) {
$scope.fadedIn = false;
});
<ul ng-controller="NavController"
class="nav-secondary actions"
darthFader fadeDuration="200"
fadeShown="fadedIn">
myApp.directive('darthFader', function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
var duration = attrs.fadeDuration || 200;
$scope.$watch(attrs.fadeShown, function(value) {
if (value)
$(element).fadeIn(duration);
else
$(element).fadeOut(duration);
});
}
};
});
If you're worried about sharing the fade in/out state between multiple controllers, you should create a service to share this state. (You could also use $rootScope and event handlers, but I generally find shared services easier to debug and test.)
myApp.value('NavigationState', {
shown: false
});
myApp.controller('NavController', function($scope, NavigationState) {
$scope.nav = NavigationState;
});
myApp.controller('OtherController', function($scope, NavigationState) {
$scope.showNav = function() {
NavigationState.shown = true;
};
$scope.hideNav = function() {
NavigationState.shown = false;
};
});
<ul ng-controller="NavController"
class="nav-secondary actions"
darthFader fadeDuration="200"
fadeShown="nav.shown">
<!-- ..... -->
<div ng-controller="OtherController">
<button ng-click="showNav()">Show Nav</button>
<button ng-click="hideNav()">Hide Nav</button>
</div>
Create a custom service, inject it in the controller. Call a method on that service that will do the fade-in/fade-out etc. Pass a parameter to convey additional information.

Resources