Skip to content

Commit

Permalink
Add support for animation via the ngAnimate module
Browse files Browse the repository at this point in the history
This breaks the "close" promise into two promises, "closing" and
"closed". The former is resolved once the modal begins closing, but the
close animation may still be in progress. The latter is resolved once
ngAnimate has finished animating the modal's removal from the DOM.

fixes #93
  • Loading branch information
lexi-lambda committed Oct 3, 2015
1 parent d02610b commit 6eeb39d
Show file tree
Hide file tree
Showing 24 changed files with 10,387 additions and 3,458 deletions.
46 changes: 37 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ First, install with Bower:
```
bower install angular-modal-service
```
or npm
or npm

```
npm install angular-modal-service
Expand Down Expand Up @@ -68,8 +68,10 @@ app.controller('SampleController', function($scope, ModalService) {

Calling `showModal` returns a promise which is resolved when the modal DOM element is created
and the controller for it is created. The promise returns a `modal` object which contains the
element created, the controller, the scope and a `close` promise which is resolved when the
modal is closed - this `close` promise provides the result of the modal close function.
element created, the controller, the scope and two promises: `close` and `closed`. Both are
resolved to the result of the modal close function, but `close` is resolved as soon as the
modal close function is called, while `closed` is only resolved once the modal has finished
animating and has been completely removed from the DOM.

The modal controller can be any controller that you like, just remember that it is always
provided with one extra parameter - the `close` function. Here's an example controller
Expand All @@ -86,10 +88,10 @@ app.controller('SampleModalController', function($scope, close) {
```

The `close` function is automatically injected to the modal controller and takes the result
object (which is passed to the `close` promise used by the caller). It can take an optional
second parameter, the number of milliseconds to wait before destroying the DOM element. This
is so that you can have a delay before destroying the DOM element if you are animating the
closure.
object (which is passed to the `close` and `closed` promises used by the caller). It can
take an optional second parameter, the number of milliseconds to wait before destroying the
DOM element. This is so that you can have a delay before destroying the DOM element if you
are animating the closure.

Now just make sure the `close` function is called by your modal controller when the modal
should be closed and that's it. Quick hint - if you are using Bootstrap for your modals,
Expand Down Expand Up @@ -149,13 +151,39 @@ The `modal` object returned by `showModal` has this structure:
to show the modal.
* `modal.scope` - The new scope created for the modal DOM and controller.
* `modal.controller` - The new controller created for the modal.
* `modal.close` - A promise which is resolved when the modal is closed.
* `modal.close` - A promise which is resolved when the modal `close` function is called.
* `modal.closed` - A promise which is resolved once the modal has finished animating out of the DOM.

#### The Modal Controller

The controller that is used for the modal always has one extra parameter injected, a function
called `close`. Call this function with any parameter (the result). This result parameter is
then passed as the parameter of the `close` promise used by the caller.
then passed as the parameter of the `close` and `closed` promises used by the caller.

### Animation

`ModalService` cooperates with Angular's `$animate` service to allow easy implementation of
custom animation. Specifically, `showModal` will trigger the `ng-enter` hook, and calling
`close` will trigger the `ng-leave` hook. For example, if the `ngAnimate` module is
installed, the following CSS rules will add fade in/fade out animations to a modal with the
class `modal`:

```css
.modal.ng-enter {
transition: opacity .5s ease-out;
opacity: 0;
}
.modal.ng-enter.ng-enter-active {
opacity: 1;
}
.modal.ng-leave {
transition: opacity .5s ease-out;
opacity: 1;
}
.modal.ng-leave.ng-leave-active {
opacity: 0;
}
```

### Error Handing

Expand Down
59 changes: 39 additions & 20 deletions dst/angular-modal-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

var module = angular.module('angularModalService', []);

module.factory('ModalService', ['$document', '$compile', '$controller', '$http', '$rootScope', '$q', '$templateRequest',
function($document, $compile, $controller, $http, $rootScope, $q, $templateRequest) {
module.factory('ModalService', ['$animate', '$document', '$compile', '$controller', '$http', '$rootScope', '$q', '$templateRequest', '$timeout',
function($animate, $document, $compile, $controller, $http, $rootScope, $q, $templateRequest, $timeout) {

// Get the body of the document, we'll add the modal to this.
var body = $document.find('body');
Expand Down Expand Up @@ -40,6 +40,17 @@
return deferred.promise;
};

// Adds an element to the DOM as the last child of its container
// like append, but uses $animate to handle animations. Returns a
// promise that is resolved once all animation is complete.
var appendChild = function(parent, child) {
var children = parent.children();
if (children.length > 0) {
return $animate.enter(child, parent, children[children.length - 1]);
}
return $animate.enter(child, parent);
};

self.showModal = function(options) {

// Create a deferred we'll resolve when the modal is ready.
Expand All @@ -66,28 +77,35 @@
// The controller can also provide a delay for closing - this is
// helpful if there are closing animations which must finish first.
var closeDeferred = $q.defer();
var closedDeferred = $q.defer();
var inputs = {
$scope: modalScope,
close: function(result, delay) {
if(delay === undefined || delay === null) delay = 0;
window.setTimeout(function() {
$timeout(function() {
// Resolve the 'close' promise.
closeDeferred.resolve(result);

// We can now clean up the scope and remove the element from the DOM.
modalScope.$destroy();
modalElement.remove();

// Unless we null out all of these objects we seem to suffer
// from memory leaks, if anyone can explain why then I'd
// be very interested to know.
inputs.close = null;
deferred = null;
closeDeferred = null;
modal = null;
inputs = null;
modalElement = null;
modalScope = null;
// Let angular remove the element and wait for animations to finish.
$animate.leave(modalElement)
.then(function () {
// Resolve the 'closed' promise.
closedDeferred.resolve(result);

// We can now clean up the scope
modalScope.$destroy();

// Unless we null out all of these objects we seem to suffer
// from memory leaks, if anyone can explain why then I'd
// be very interested to know.
inputs.close = null;
deferred = null;
closeDeferred = null;
modal = null;
inputs = null;
modalElement = null;
modalScope = null;
});
}, delay);
}
};
Expand All @@ -110,18 +128,19 @@
// Finally, append the modal to the dom.
if (options.appendElement) {
// append to custom append element
options.appendElement.append(modalElement);
appendChild(options.appendElement, modalElement);
} else {
// append to body when no custom append element is specified
body.append(modalElement);
appendChild(body, modalElement);
}

// We now have a modal object...
var modal = {
controller: modalController,
scope: modalScope,
element: modalElement,
close: closeDeferred.promise
close: closeDeferred.promise,
closed: closedDeferred.promise
};

// ...which is passed to the caller via the promise.
Expand Down
4 changes: 2 additions & 2 deletions dst/angular-modal-service.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dst/angular-modal-service.min.js.map

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions samples/custom/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#overlay {
position: fixed;
left: 25%;
top: 25%;
padding: 25px;
border: 2px solid black;
background-color: #ffffff;
width: 50%;
height: 50%;
z-index: 100;
}
#fade {
position: fixed;
left: 0%;
top: 0%;
background-color: black;
-moz-opacity: 0.7;
opacity: .70;
filter: alpha(opacity=70);
width: 100%;
height: 100%;
z-index: 90;
}

#custom-modal.ng-enter {
transition: opacity .5s ease-out;
opacity: 0;
}
#custom-modal.ng-enter.ng-enter-active {
opacity: 1;
}
#custom-modal.ng-leave {
transition: opacity .5s ease-out;
opacity: 1;
}
#custom-modal.ng-leave.ng-leave-active {
opacity: 0;
}
37 changes: 7 additions & 30 deletions samples/custom/custom.html
Original file line number Diff line number Diff line change
@@ -1,31 +1,8 @@
<style type="text/css">
#overlay {
position: fixed;
left: 25%;
top: 25%;
padding: 25px;
border: 2px solid black;
background-color: #ffffff;
width: 50%;
height: 50%;
z-index: 100;
}
#fade {
position: fixed;
left: 0%;
top: 0%;
background-color: black;
-moz-opacity: 0.7;
opacity: .70;
filter: alpha(opacity=70);
width: 100%;
height: 100%;
z-index: 90;
}
</style>
<div id="overlay" ng-show="display">
This is a custom modal. The angular-modal-service doesn't depend on bootstrap, you
can use any modal you want.
<a href ng-click="close()">Close</a>
<div id="custom-modal">
<div id="overlay">
This is a custom modal. The angular-modal-service doesn't depend on bootstrap, you
can use any modal you want.
<a href ng-click="close()">Close</a>
</div>
<div id="fade"></div>
</div>
<div id="fade" ng-show="display"></div>
9 changes: 2 additions & 7 deletions samples/custom/customcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ var app = angular.module('sampleapp');

app.controller('CustomController', ['$scope', 'close', function($scope, close) {

$scope.display = true;
$scope.close = close;

$scope.close = function() {
$scope.display = false;
close();
};

}]);
}]);
8 changes: 5 additions & 3 deletions samples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
<script src="vendor/bootstrap/js/bootstrap.min.js"></script>
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular/angular-animate.js"></script>
<script src="angular-modal-service.js"></script>
<script src="sampleapp.js"></script>
<script src="yesno/yesnocontroller.js"></script>
<script src="complex/complexcontroller.js"></script>
<link href="custom/custom.css" rel="stylesheet">
<script src="custom/customcontroller.js"></script>
<style>
body {
Expand Down Expand Up @@ -94,9 +96,9 @@ <h3>A Simple Yes/No Modal</h3>
<h3>A Complex Modal</h3>

<p>This more complicated modal contains a form, showing how the modal template can
be customised. It also has a controller with data injected by the caller and
be customised. It also has a controller with data injected by the caller and
returns more complex data.</p>

<p><a href class="btn btn-default btn-lg " ng-click="showComplex()">Show Complex</a></p>

<pre ng-show="complexResult">{{complexResult}}</pre>
Expand All @@ -115,7 +117,7 @@ <h3>A Complex Modal</h3>
<h3>A Custom Modal</h3>

<p>This sample shows how you can use any kind of modal, not just Bootstrap modals.</p>

<p><a href class="btn btn-default btn-lg " ng-click="showCustom()">Show Custom</a></p>

<pre ng-show="customResult">{{customResult}}</pre>
Expand Down
6 changes: 3 additions & 3 deletions samples/sampleapp.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Build our app module, with a dependency on the angular modal service.
var app = angular.module('sampleapp', ['angularModalService']);
var app = angular.module('sampleapp', ['angularModalService', 'ngAnimate']);

app.controller('SampleController', ['$scope', 'ModalService', function($scope, ModalService) {

$scope.yesNoResult = null;
$scope.complexResult = null;
$scope.customResult = null;
Expand Down Expand Up @@ -51,4 +51,4 @@ app.controller('SampleController', ['$scope', 'ModalService', function($scope, M

};

}]);
}]);
Loading

0 comments on commit 6eeb39d

Please sign in to comment.