I have a feature that I am building that is about 90% of the functionality the Angular-UI Bootstrap Modal provides. The only difference is:
The modal needs to be positioned relative to a container div (I've already handled the styling aspect with the windowClass)
At issue is that the $modalStack service hard codes the body.append call, as seen here:
body.append(modalDomEl)
body.addClass(OPENED_MODAL_CLASS)
Step one is here
I started by simply copying / pasting the open methods on each service (renaming as openWithinElement), and modifying what I needed to make it work. As you can see, if you run the app, it throws an error on the $q dependency not being defined. Okay, I guess that makes sense.
Next I added the dependencies to the provider.decorator method, which bypassed all dependency errors. But then I was getting another error, function getTemplatePromise is undefined ... well of course it isn't defined. It's an internal method to the service.
That's when I got truly stuck. It seems impossible to decorate this service with a new method, without redefining all of the private functions / objects / props within the service.
Is that the case?
[EDIT TO ADD]
I ultimately ended up decorating the service with an updateParentElement method, which then moved the modal to the passed in element, which was executed in the .opened promise. It's hacky, but works, but I'm hoping there's something I'm missing with decorators.
[/EDIT]
The simplest solution as I see it will be to provide specific windowClass, then after dialog is open find its element by that class and move it to the container.
Related
I'm looking for at way to track down the pesky error of
apply/digest is already in progress
I'm familier with not using the anti-pattern and have check my codebase for nested apply's/digest's.
The problem lies in third-party plugins in this case FormEditor and Flatpickr. I have nested FlatPickr (with angular add-on) into a formEditor cshtml file which gives me the pesky error.
Is there a way to track the location of all the invokation of apply and/or digest that are present in my project?
Or does anyone have a solution to formEditor with flatPickr and flatpickr angular add-on?
FormEditor: https://github.com/kjac/FormEditor FlatPickr: https://github.com/chmln/flatpickr FlatPickr add-on: https://www.npmjs.com/package/angular-flatpickr
SOLUTION:
The problem was a $apply called by an eventListener which injected the apply into the running apply/digest. Used $timeout as suggested in the answer marked as correct.
The location was found by looking into the error log as suggested in comments
AngularJS automatically triggers a $digest cycle in many cases (after ng-click triggered for example) so the solution is not to find all the "apply / digest" in your code because it won't help you to prevent this error.
The right approach is to control 3rd parties calls to $apply method.
One approach can be wrapping $apply call with a safety check:
if(!$scope.$$phase) {
// place 3rd party updates to scope here
$scope.$apply();
}
The problem with this method is that sometimes you code won't be called.
The better approach will be to wrap it with $timeout 0:
$timeout(function(){
// place 3rd party updates to scope here
});
This way you merged more naturally into angular digest cycle.
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 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).
I have a question related to modules and routing in angularjs. The requirements are to define all routing configs in one module - lets call this ModuleRtr.
Once the route is triggered, it should call a controller (called TestCtr for example). This controller is defined in another module - let's call it ModuleCtrl.
If I try to do that, I got an exception like:
"Argument TestCtr is not a function, got undefined"
. The reason is that when angular broadcast $routeChangeSuccess event, the listener tries to access the controller (TestCtr) as part of the current module - ModuleRtr. Because it's not there, it throws an error.
If I simply create TestCtr controller in ModuleRtr (the same that has the route in it), then everything works. But I don't want that, I want to have my TestCtr in different module. I also don't want to have route define in ModuleCtrl module.
So, in more straightforward way, my question is: Is it possible a route defined in one module (ModuleRtr) to call a controller defined in another module (ModuleCtrl)?
Something that I forgot to mention...I don't want to bind these 2 modules by any means. That of course includes listing a dependency during module's creation. I have already tried dynamic loading of TestCtr - it didn't solve the problem.
The scenario is: You have 2 angularjs applications (modules). They don't know anything about each other...except one thing - somewhere in a shared/common location, there is an object which follows a particular structure (like a contract). The first module writes data there. Then it runs a common function from the second module (this is actually the question I am asking - how?). Then the second module knows already where to go (it's a contract) what to read and what to do. One way I can do this is to try dynamically generates the string that represents a module dependency when the other module is created. I am not sure if that is a good solution...
Many thanks!
You can use a single module with submodules for each functionality, such as:
angular.module('myApp.home',[]);
angular.module('myApp.routing',[]);
...modules here...
angular.module('myApp', [
'myApp.home',
'myApp.routing'
]);
So you can send a method from myApp.home calling a intermediate method from myApp and this one calling the myApp.routing method.
At least, this is how I see it.
I am seeing some strange behavior that I'm hoping someone can explain.
From the render method in a backbone view I have been attempting to do the following:
this.$(".msg").colorbox();
And
this.$el.find(".msg").colorbox();
However in both cases, although the msg elements are located, when trying to invoke the colorbox method on the returned elements I get an exception that the method is not defined.
However when I use:
$(this.el).find(".msg").colorbox();
All is well. Is anyone aware of why this might be?
It is a common issue. Surely colorbox is a jQuery plugin. The jQuery plugin is injected until the View's Element is added to the DOM of your Page.
I mean, your code is normal to fail with the natural behavior.
$('body').append( view.render().el );
But if you do this, it will works:
$('body').append( view.el );
view.render();
Third party Backbone.js Plugins has a method "named" onRender that is executed after render the View(and assuming that added to the DOM). But if you do not work with additional Backbone.js Plugins just be sure to call the colorbox until your View was added to the DOM.
Shooting in the dark...
this.$el and $(this.el) are differente instances even if both make reference to the same DOM element.
Maybe since the moment Backbone did precompile this.$el = $(this.el) to the moment you call this.$el.colorbox() something has happend so this function is not available and it has never been available in the instance this.$el.
I don't know what is colorbox() but if this is part of a third part jQuery plugin can be important the order in that your JS code is loaded?