core
core

Reputation: 33059

angular-ui bootstrap $modal service using directive instead

The examples I see of using angular-ui/bootstrap's $modal always look something like this:

    $modal.open({
        templateUrl: 'modaltemplate.html',
        controller: function($scope) {
            ...
        }
    });

What if I want to use a directive, instead? Like this:

    $modal.open({
        template: '<my-modal-directive></my-modal-directive>'
        // no "controller" property; use directive's controller
    });

The markup for my-modal-directive renders fine, and I've moved the controller property into the my-modal-directive definition object, but now getting this error from the my-modal-directive:

Error: [$injector:unpr] Unknown provider: $modalInstanceProvider <- $modalInstance

Can anyone point me to an example where $modal uses a directive where that directive defines the controller?

For example, this works, where I've replaced the templateUrl with a directive:

http://plnkr.co/edit/YrGaF83GH6bzZPRR55GK?p=preview

But when I move the controller from $modal.open() into the directive, that's when the error happens:

http://plnkr.co/edit/aLBT239EpL004DRh4jll?p=preview

Upvotes: 7

Views: 28661

Answers (5)

Karol Websky
Karol Websky

Reputation: 116

It's even more late reply, but someone may find it useful.

I have enhanced Fernando Felix answer and made my own quite flexible directive which communicates with the controller, which I think might be solution for this question.

Directive

var modalUrl = function ($modal) {
    return {
        restrict: 'A', // A: attribute
        scope: { // isolate scope
            'modalUrl': '@', // modal view url to render the modal content
            'modalController': '@', // modal view controller (optional)
            'value': "="
        },
        link: function(scope, element, attrs){
            console.log('modalUrl link');

            var modalInstance;
            var template = [
                    '<div class="modal-body">',
                        '<button ng-click="$close()" type="button" class="close" aria-label="Close">',
                            '<span aria-hidden="true">&times;</span>',
                        '</button>',
                        '<div ng-include="\'' + scope.modalUrl + '\'"></div>',
                    '</div>'
                ].join('');


            element.bind('click', function(){
                // see modal reference from ui bootstrap at <http://angular-ui.github.io>
                modalInstance = $modal.open({
                    size: attrs.size,
                    animation: true,
                    template: template,
                    resolve: {
                        params: function () {
                            console.log('value passed to modal:');
                            console.log(scope.value);
                            return scope.value;
                        }
                    },
                    controller: scope.modalController
                });

                modalInstance.result.then(
                    function (returnValue) {
                        // alert('value: '+returnValue);
                        console.log('modal returnValue:');
                        console.log(returnValue);
                        scope.value = returnValue;
                    }, function () {
                        console.log('Modal dismissed at: ' + new Date());
                    }
                );

            });

        }
    };
}
modalUrl.$inject = ['$modal'];
angular.module('app').directive('modalUrl', modalUrl);

Controller

var HelloCtrl = function ($scope, $modalInstance, modalVal) {
    // $modalInstance: same from  from ui bootstrap
    console.log('Hello init!');
    // modalVal is the init modal value passed via directive
    console.log(modalVal);

    // your code
    $scope.name = modalVal;

    $scope.ok = function() {                                        
        $modalInstance.close(this.name); // returnValue
    };

    $scope.cancel = function() {
        $modalInstance.dismiss('cancel');
    };
}
HelloCtrl.$inject = ['$scope', '$modalInstance','params'];
angular.module('app').controller('HelloCtrl',HelloCtrl);

inline template

<script type="text/ng-template" id="hello.html">
    <div class="modal-header">
        <h3 class="modal-title">I'm a modal!</h3>
    </div>
    <div class="modal-body">
        <input type="text" ng-model="name" />                                        
    </div>
    <div class="modal-footer">
        <button class="btn btn-primary" ng-click="ok()">OK</button>
        <button class="btn" ng-click="cancel()">Cancel</button>
    </div>
</script>

It's one controller and template per popup type, then you can call it multiple times with:

<a modal-url="hello.html" modal-controller="HelloCtrl" value="yourVal" ng-init="yourVal='test'" href="#">Click here to open the modal</a>

You can initialize value with whatever - ie. object, array etc.

or external template

Pretty much the same, just url changes and template file is used for template.

<a modal-url="/modal/test1.html" modal-controller="HelloCtrl" value="yourVal" ng-init="yourVal='test'" href="#">Click here to open the modal</a>

test1.html

<div class="modal-header">
    <h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
    <input type="text" ng-model="name" />                                        
</div>
<div class="modal-footer">
    <button class="btn btn-primary" ng-click="ok()">OK</button>
    <button class="btn" ng-click="cancel()">Cancel</button>
</div>

Modal size etc.

Just add parameter size="sm|lg" for the modal link/button ie. Click here to open the modal For standard size skip the parameter. You may enhance it yourself using link function attrs.

Upvotes: 2

Dimpu Aravind Buddha
Dimpu Aravind Buddha

Reputation: 9835

I'm kanda late replay put simplest way is to use

$scope.$parent.$close(result);

$scope.$parent.$dismiss(reason);

This works form your directive controller.

Upvotes: 1

Fernando Felix
Fernando Felix

Reputation: 91

I create a directive to create modals easily. A modal content is based on a template view.

angular.module('your_app').directive('modalViewUrl', function ($modal) {

    return {
        restrict: 'A', // A: attribute
        scope: { // isolate scope
            'modalViewUrl': '@', // modal view url to render the modal content
            'modalController': '@' // modal view controller (optional)
        },
        link: function($scope, element, attrs){

            element.bind('click', function(){

                var template = 
                    '<div class="modal-body">' + 
                    '<button ng-click="$close()" type="button" class="close" aria-label="Close">' +
                    '<span aria-hidden="true">&times;</span>' +
                    '</button>' +
                    '<div ng-include="\'' + $scope.modalViewUrl + '\'"></div>' +
                    '</div>';

                // see modal reference from ui bootstrap at <http://angular-ui.github.io>
                var modalInstance = $modal.open({
                    animation: true,
                    template: template,
                    controller: $scope.modalController,                    
                });
            });
        }
    };
});

Example how to use it:

index.html

<a modal-view-url="hello.html" modal-controller="HelloCtrl" href="#">
    Click here to open the modal
</a>

hello.html

<h1> Hello World {{name}} </h1>

HelloCtrl.js

angular.module('yourApp').controller('HelloCtrl', 
                function ($scope, $modalInstance) {
    // $modalInstance: same from  from ui bootstrap
    $scope.name = "Xico";
});

A modal view can have its own controller. Example:

hello.html (modified)

<h1 ng-controller="Hello2Ctrl"> {{msg}} {{name}} </h1>

Hello2Ctrl.js

angular.module('yourApp').controller('Hello2Ctrl', 
                function ($scope) {
    $scope.msg = "Hello Worldsszz";
    $scope.name = "Zefa";
});

Observe that the modal output will be "Hello Worldsszz Xico", because the modal controller (HelloCtrl) will be rendered after view controller (Hello2).

Reference

Upvotes: 3

Sergiu Paraschiv
Sergiu Paraschiv

Reputation: 10153

The problem is that $modalInstance can only be injected in the controller that you provide to $modal.open. Check out the sources here:

$modal.open = function (modalOptions) {
    ...
    var modalInstance = {
        ...
    };
    ...
    if (modalOptions.controller) {
        ...
        ctrlLocals.$modalInstance = modalInstance;
        ...
        ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
        ...
    }
    ...
}

In essence when you try to add $modalInstance as a dependency to your controller AngularJS looks for a registered global provider named $modalInstanceProvider. Now the trouble is, if you understood the code above, that $modalInstance is not a globally registered provider. It only "exists" as a dependency for the controller you pass to $modal.open.

If you read the rest of the code you'll notice that $modal.open returns modalInstance, maybe you can use that.

Something like this:

function SomeController($modal) {
    $scope.modal = {
        instance: null
    };

    $scope.modal.instance = $modal.open({
        template: '<my-modal-directive modal="modal"></my-modal-directive>',
        scope: $scope
    });
}

function MyModalDirective() {
    scope: {
        modal: '='
    },
    link: function($scope) {
         // here you can access $scope.modal.instance
    }
} 

Upvotes: 6

thedoctor
thedoctor

Reputation: 1508

The issue you have is that you are trying to inject values which are not available for injection. Only values registered with the injector can be injected.

The logic of you code is also flawed, you are creating the modal in your main controller but trying to close it in the directive. Ideally, the modal should be triggered by the directive (via it's link function), and then you can ok/cancel it from there.

See my http://plnkr.co/edit/3p1rXAymd7BilyklgxKy?p=preview for one possible approach, I have kept the code that closes and cancels the modal in the main controller.

    angular.module('ui.bootstrap.demo', ['ui.bootstrap']);
angular.module('ui.bootstrap.demo').directive('myModal', function() {
    return {
        restrict: 'E',
        templateUrl: 'myModalContent.html',
        controller: function ($scope) {
          $scope.selected = {
            item: $scope.items[0] 
          };
        }
    };
});
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $log) {

  $scope.items = ['item1', 'item2', 'item3'];

  $scope.open = function (size) {
    var modalInstance;
    var modalScope = $scope.$new();
    modalScope.ok = function () {
            modalInstance.close(modalScope.selected);
    };
    modalScope.cancel = function () {
            modalInstance.dismiss('cancel');
    };      

    modalInstance = $modal.open({
      template: '<my-modal></my-modal>',
      size: size,
      scope: modalScope
      }
    );

    modalInstance.result.then(function (selectedItem) {
      $scope.selected = selectedItem;
    }, function () {
      $log.info('Modal dismissed at: ' + new Date());
    });
  };
});

Upvotes: 5

Related Questions