Mauro
Mauro

Reputation: 4224

What's the nicest way to wait for a promise initiated on a directive to resolve?

This is a simple form that contains one input field with a directive. If the input is valid, the directive will call a remote server. The response will populate the form model with some extra data.

How do I wait for the promise to resolve when the form is submitted?

Here's some sample code in case it helps:

app.directive('getSecretStuff', function($q, $http) {

    function get_some_secret_stuff( value ) {

        var deferred = $q.defer();

        $http.get(url)
        .success(function (response) {
            deferred.resolve(response.secret_stuff);
        });

        return deferred.promise;

    };

    return {
        restrict: 'A',
        require: '^form',
        scope: {
            secret_stuff: '=stuff',
        },
        link: function(scope, element, attrs, ctrl) {
            scope.$watch(
                function() { return ctrl[ element.attr('name') ].$valid; },
                function (validity) {
                    if (validity) {
                        get_some_secret_stuff( element.val() ).then(function( stuff ) {
                            scope.secret_stuff = stuff;
                        });
                    }
                }
            );

        },
    };
});

Upvotes: 1

Views: 1013

Answers (1)

potatopeelings
potatopeelings

Reputation: 41065

(Mis)using Async Validators to Disable Form Submit till a Promise Resolves

You can (mis)use an async validator to do this. Set the form model with extra in the async validator promise and then use the form's $pending to disable the form's submit button till the promise resolves (if it needs to)

HTML

<div ng-app="myApp">
    <div ng-controller="ctrlr">
        <form name="form" novalidate ng-submit="!form.$pending && submit()">
            <!-- our validaty check is just a sample - check for numbers -->
            <input ng-model="publicStuff" name="publicStuff" get-secret-stuff stuff="secretStuff" ng-pattern="/^[0-9]+$/" />
            <input ng-model="secretStuff" />
            <button type="submit">Go</button>
        </form>
    </div>
</div>

Script

angular.module('myApp', [])
    .controller('ctrlr', function ($scope) {
    })
    .directive('getSecretStuff', function ($q, $timeout) {
        function get_some_secret_stuff(value) {
            var deferred = $q.defer();
            // for illustration, we use the $timeout instead of $http 
            // swap this out with the actual $http call in your code
            $timeout(function () {
                deferred.resolve('secret value for ' + value);
            }, 2000)
            return deferred.promise;
        };

        // a promise that resolves immediately
        function get_dummy_promise() {
            var deferred = $q.defer();
            console.log('asd')
            deferred.resolve('1');
            return deferred.promise;
        };

        return {
            restrict: 'A',
            require: ['^form', 'ngModel'],
            scope: {
                secret_stuff: '=stuff',
            },
            link: function (scope, element, attrs, ctrlrs) {
                // we misuse the async validator to... 
                ctrlrs[1].$asyncValidators.secretStuff = function (modelValue, viewValue) {
                    if (viewValue)
                        return get_some_secret_stuff(viewValue).then(function (response) {
                            // ...do work other than validation
                            scope.secret_stuff = response;
                            return true;
                        });
                    else
                        // this is just for the first run when the validator gets called even if $valid is not set
                        return get_dummy_promise().then(function (response) {
                            scope.secret_stuff = undefined;
                            return true;
                        });
                };

                // we probably want to clear out the secret stuff for invalid values
                scope.$watch(
                    function () { return ctrlrs[0][element.attr('name')].$valid; },
                    function (validity) {
                        if (!validity) {
                            scope.secret_stuff = undefined;
                        }
                    }
                );
            },
        };
    });

Upvotes: 2

Related Questions