I have some code that creates and registers some animations inside a controller. Unfortunately, whenever I register an animation inside a controller, it does not seem to initialise (The function never gets called.)
I have created a plunker to illustrate the issue. In this, I call"createAnim1" outside the controller and the animation functions correctly. I call "createAnim2" inside a controller, and the animation does not function.
createAnim1();
app.controller('main', function($scope) {
createAnim2(); //This animation does not work
});
The createAnim function are in the form:
app.animation('.anim1', function() {
return {
...
}
});
Am I missing something obvious? The $animate and $animateProvider documentation don't seem to contain anything relevant to this.
I assume this has to do with the fact that a compilation has already happened, and some step required to initialise the animations is being missed? Is there some way to get around this?
PS: I have only been using angular since last week, so I'm not well versed on the internals.
Edit:
After the comments from Alex, I think it is worth clarifying what I am trying to do:
I have some code which generates css class animation selectors and appends them to the document head. If the browser does not support transitions, the code generates a jquery animation equivalent. I have tried to create a factory to expose this code via DI, and it is when this code is run (From inside a controller) the animation is never able to trigger (The initialisation code is never run).
Related
I am having trouble understanding using jQuery with Angular's digest cycle.
I am using an ng-repeat= to load content. the content (using $compile) ends up being a div with with two child divs and several grandchild elements. after loading the arrary for the ng-repeat, I want to use jQuery's children.each() like so:
callBack = function(index) {
var objParent = $('#' + $scope.panes[index].windID); //entire window
var objChild = [];
//objChild[0] = title bar
//objChild[1] = contentpane
objParent.children().each(function(i){
objChild[i] = $(this);
});
//more code
}
The problem is that as my open window method got larger with more features, somehow Angular's digest started working differently. What I mean is that before I added my maximize/restore/minimize methods and buttons to match, when my controller got to the code above, the window was already digested through scope and showed in the dom (although hidden with {display: none} for jQuery's fade in) meaning that the above code worked.
However now that there is more going on to be digested, when it gets to that point the digest is not complete, and the child finder fails since that DOM has not been updated yet. I have to use $timeout, and I increasingly have to increase the time. I have to wait around 600ms right now, and I feel that is to slow from icon click to window showing.
Now, If I add a $scope.$apply() right before the $timeout(), it acts like it's just forcing the digest to hurry up. I can reduce the time back to 0ms, and everything works instantly. Yet I get a digest in progress error. I thought the $timeout waited for the end of the digest anyways?
I've tried various methods, even $$postDigest, but the only thing that seems to work is forcing an $apply resulting in a digest in progress error that the end user will never see nor even care about since it actually makes it all work. That would not me so much of a problem, but it bugs me because after opening and closing my window "panes" the digest in progress error can multiply in the console.
Here's a link to a little bit older version: AkadineWebOS
The app is more jQuery than Angular, I'm just using Angular for it's http module and to make it easy to push and splice my window array and let the ng-repeat magic handle the dom. jQuery does all the heavy lifting with dragging, resizing, fadein/out, centering windows on open, and most everything else.
So here is my question: Can someone please explain to me what I am missing about the way that Angular's digest works?
As Claise said, jQuery is not asyc like angular. I actually just had to change the $timeout() to a plain ol' setTimeout(), basically asycing my jQuery callback. Now it works great!
Edit: DON'T DO THIS. It's a waste of time unless you want to spend hours debugging, turns out this is a lot more complicated than I first thought. For now the solution to this is:
Move the loading of resources out of the resolvers and into the controller.
Remove all enter animations handled by ui-router.
Add your own animation init and enter classes to the main scope with ng-class.
Use $scope.$emit from the page-specific controllers to tell the main controller when the stuff has finished loading.
In short, if you need this (I have seen a few questions on the ui-router issue tracker) don't use resolvers or ng-animate for the enter animations. You also can't do it on the $stateChangeStart event and the leaving animations as this collides with how ui-router works.
Below is my original question.
I have a specific use case where I need the page transitions and resolves to happen in a certain order currently they happen like this:
resolve > animate out > animate in
I need it more like this:
animate out > resolve > animate in
I decided to check out the ui-router source code and find out why it behaves they way it does. Fortunately it's a very simple mod. In the ui-view directive we have this code.
scope.$on('$stateChangeSuccess', function() {
updateView(false); // cleanupLastView(); is at the end of this function
});
scope.$on('$viewContentLoading', function() {
updateView(false); // cleanupLastView(); is at the end of this function
});
I need to updated it to:
scope.$on('$stateChangeStart', function() {
cleanupLastView();
});
scope.$on('$stateChangeSuccess', function() {
updateView(false); // remove cleanupLastView();
});
scope.$on('$viewContentLoading', function() {
updateView(false); // remove cleanupLastView();
});
The problem is that for obvious reasons, I don't want to go and hack the core. Is there any way to "de-register" the ui-router's ui-view directive and tell it to use one of mine instead?
So I actually wrote this question up and then found an answer. Posting it for anyone else on their travels around the internet.
The answer was to copy BOTH ui-view directives and simply add a new directive called (for example) rich97-view. Then you can use it in your view as if you were using ui-view. The great thing about this methods is that the mod only applies where you need it to, the default behavior is unchanged.
You have probably heard it as many times as I have. "Do all your DOM manipulation in directives". But no one ever seems to say what could happen if you actually do DOM manipulation outside a directive in Angular.
I have a problem that I managed to reproduce in this Plunk
I have made a very simple directive that just outputs the element to the console.
app.directive('dirre', function(){
return {
link: function(scope, element, attrs){
console.log({message:"dirrens linkFn", element: element, count: element.length})
}
}
});
I have two identical jquery UI accordions, the only difference is the way they are called. One is called in a controller and the other one in a directive. Calling accordion from a controller is of course something bad.
As you can see if you run the application there is a situation where one of the dirre-directives does not seem to have an element but there are no errors.
The same thing happens in a big application I'm working with right now. The problem seems to be that someone in our team decided to call Jquery UI's accordion in a controller and not in a directive.
I haven't been able to step through the code to see what actually happens but I strongly suspect that the DOM is modified while Angular is compiling and something goes wrong.
Is this a plausible explanation?
Is this an example of what can go wrong if you do DOM manipulations outside a directive?
The controller and the directive links function are called asynchronously.
Usually you can see directives being built before the main Controller complete. When the controller terminates, the directives update their watched variable (ngModel, $watch(something)...). Basically this is done with promises.
The link/compile function however is not called again. You have to compile, watch, apply the new DOM. Which basically means writing the similar code to angularjs.
I've recently learned a lot about testing controllers w/ modals, modal controllers and directives. Almost everything in my app that can be tested, is being tested and it's all rainbows and kittens.
My latest (seemingly impossible) problem is that the modal I'm testing has a sort of "wizard" UI, where the user progresses through each step to create something to submit when the modal is closed. I'd like to test that the UI is reacting accordingly.
For example, in the modal controller, when $scope.cloneType is false, I'd like to make sure that my div#cloneMenu is actually showing (or hidden when cloneType has been set).
In a directive, I can do something like this:
$el.isolateScope().cloneType = null;
$scope.$digest();
expect($el.find('#cloneMenu').hasClass('ng-hide')).toBe(false);
$el.isolateScope().cloneType = 'basic';
$scope.$digest();
expect($el.find('#cloneMenu').hasClass('ng-hide')).toBe(true);
but I don't have $el (or anything like it) available in the modal controller (that I know of). I tried using $document.find() but that didn't work. I'm suspecting that $compile comes in to play at some point, but don't know where.
So, aside from just making the body of my modal a directive and unit testing that, is there any way of testing the DOM in the view of an AngularUI modal dialog? Any code samples or links to info that can help would be appreciated.
I'm using Angular 1.2.9 and Jasmine 2.0 and I've included jQuery so that I have a better find() method. (Being limited to tagName is lame when trying to test/traverse real DOM).
Thanks!
I'll provide an answer just so this question has the conclusion that I was after.
I think Sunil D. has it right with testing the views via an E2E test.
I was forcing myself to see the modal as a 'component' like a directive and so wanted to write tests in that way. I know it's not a directive, but it just felt more natural to test it that way because of how it works/looks/is similar to a directive (controller fn + template).
The other issue was that the controller for this thing is so small (because angular is so awesome) so there was only a couple of tests. I felt there should be more to it and wasn't getting the sense of completeness/accomplishment that I did from writing directive tests.
In the end, I'm just going to finish the tests for the modal controller and look into getting some E2E in the mix. That was next on my list anyway.
I have multiple custom directives in my ngApp. The demo code is:
<top-nav></top-nav>
<left-sidebar></left-sidebar>
<div class="content">
....
</div>
The height of the 'left-sidebar' needs to be adjusted according to the height of the 'top-nav'.
Similarly there are other interdependent UI tasks. I want to run the post-load code (say app.initializeUI();) only after ALL the directives have been loaded and rendered.
Now, How do I achieve that?
EDIT :
Currently I am using the following code :
App.run(function($timeout){
$timeout(function(){ app.init() },0);
});
This runs fine, however, I am not sure this is the perfect way of doing this.
EDIT 2:
For people who want to avoid setting styles in js - use CSS Flexbox. I find it much better than calculating heights post page load. I got a good understanding of flexbox here
I would create an attribute directive with isolated scope, set terminal=true and add it to your body tag. Then in your link function, setup a $watch - isInitialized which is initially false. In your $watch handler, call your app.init(), and then de-register the $watch, so that it is always initialized once.
Setting up a terminal directive has consequences - no other directive can run after the terminal directive on the same element. Make sure this is what you want. An alternative is to give your directive the lowest possible value so that it is compiled first, and linked last.
The important pieces to this solution are:
By adding your Directive to the body tag, you ensure that it is linked last.
By adding a $watch, you ensure that all other directives have gone through a digest cycle, so by the time your $watch handler is called, all other directives should have already rendered.
Note of caution: The digest cycle may run several times before scope changes stabalise. The above solution only runs after the first cycle, which may not be what you want if you really want the final rendering.