45

I'd like to use angular component with ui.bootstrap.modal. angular version is 1.5.
I tried to use like below.

component

function MyComponentController($uibModalInstance){
  var ctrl = this;

  ctrl.doSomething = function() {
    //doSomething
  }
}

app.component('myComponent', {
  contoller: MyComponentController,
  templateUrl: '/path/to/myComponent.html'
}

parent controller

function parentController($uibModal){
  var ctrl = this;

  ctrl.openModal = function(){
    var modalInstance = $uibModal.open({
      template: '<my-component></my-component>'

  }
}

And when I execute parentController.openModal() , I got the error of $injector:unpr Unknown Provider although modal window is open.
Is there a way to use angular component with ui.bootstrap.modal? If you need more information, please let me know that.
Thank you.

EDIT
I've got a way to use component with ui.bootstrap.modal from Renato Machado, Thanks Renato.
But I feel It's a little bit complicated and not convenient. So finally I think that it's better to use component inside the modal.
Modal is opened with regular way(just set controller and template in $uibModal.open({}) ) and the modal contains the component which has logics you want to use commonly.
Modal should have only simple logics that are related with modal like close modal window.
Another logics mainly related with business/Application should be in component.
It makes easy to commonalize.

wtadahiro
  • 685
  • 1
  • 8
  • 13
  • do you inject dependency for ui.bootstrap in your app? – Hadi J Apr 04 '16 at 11:29
  • Yes, I've already injected like below. var app = angular.module('clientApp', [ 'ngAnimate', 'ngCookies', 'ngResource', 'ngRoute', 'ngSanitize', 'ngTouch', 'LocalStorageModule', 'ui.bootstrap' ]) – wtadahiro Apr 04 '16 at 11:30
  • Have you included all the files in your project ? – Jyoti Prakash Apr 04 '16 at 11:34
  • @Jyoti Yes, all files are included in my project. So I can open modal window, but I got the error:( – wtadahiro Apr 04 '16 at 11:37
  • "component (Type: string, Example: myComponent) - A string reference to the component to be rendered that is registered with Angular's compiler." Can be found here : https://angular-ui.github.io/bootstrap/#!#options-parameter :) – Disfigure Mar 09 '17 at 17:07

4 Answers4

71

EDIT: As of UI Bootstrap 2.1.0 there is native support for component in bootstrap modals. It looks like there have been several quick releases after 2.1.0 to fix some issues with the modals, so I'd be sure to grab the latest.

See this Plunk for a version using UI Bootstrap 2.1.0+

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

angular.module('app', ['ngAnimate', 'ui.bootstrap']);

angular.module('app')
  .component('myContent', {
     template: 'I am content! <button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open Modal</button>',
     controller: function($uibModal) {
        $ctrl = this;
        $ctrl.dataForModal = {
        name: 'NameToEdit',
        value: 'ValueToEdit'
     }

    $ctrl.open = function() {
      $uibModal.open({
         component: "myModal",
         resolve: {
           modalData: function() {
             return $ctrl.dataForModal;
           }
         }
       }).result.then(function(result) {
            console.info("I was closed, so do what I need to do myContent's  controller now.  Result was->");
      console.info(result);
       }, function(reason) {
           console.info("I was dimissed, so do what I need to do myContent's controller now.  Reason was->" + reason);
       });
    };
  }
});

angular.module('app')
  .component('myModal', {
template: `<div class="modal-body"><div>{{$ctrl.greeting}}</div> 
<label>Name To Edit</label> <input ng-model="$ctrl.modalData.name"><br>
<label>Value To Edit</label> <input ng-model="$ctrl.modalData.value"><br>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleClose()">Close Modal</button>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleDismiss()">Dimiss Modal</button>
</div>`,
  bindings: {
    modalInstance: "<",
    resolve: "<"
  },
  controller: [function() {
    var $ctrl = this;
    $ctrl.$onInit = function() {
      $ctrl.modalData = $ctrl.resolve.modalData;
    }
    $ctrl.handleClose = function() {
      console.info("in handle close");
      $ctrl.modalInstance.close($ctrl.modalData);
    };
    $ctrl.handleDismiss = function() {
      console.info("in handle dismiss");
      $ctrl.modalInstance.dismiss("cancel");
    };
  }]
});

Original answer is below:

I was trying to figure this out the other day too. I took the information I found in this post along with this link to try and come up with an alternate way to accomplish this. These are some reference links I found that helped me:

https://github.com/angular-ui/bootstrap/issues/5683

http://www.codelord.net/ (this one helped in understanding passing arguments to callbacks in components)

Also here is a Plunk: http://plnkr.co/edit/PjQdBUq0akXP2fn5sYZs?p=preview

I tried to demonstrate a common real world scenario of using a modal to edit some data.

angular.module('app', ['ngAnimate', 'ui.bootstrap']);

angular.module('app')
.component('myContent', {
    template: 'I am content! <button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open Modal</button>',
    controller: function($uibModal) {
        $ctrl = this;
        $ctrl.dataForModal = {
            name: 'NameToEdit',
            value: 'ValueToEdit'
        }
        $ctrl.open = function() {
            $uibModal.open({
                template: '<my-modal greeting="$ctrl.greeting" modal-data="$ctrl.modalData" $close="$close(result)" $dismiss="$dismiss(reason)"></my-modal>',
                controller: ['modalData', function(modalData) {
                    var $ctrl = this;
                    $ctrl.greeting = 'I am a modal!'
                    $ctrl.modalData = modalData;
                }],
                controllerAs: '$ctrl',
                resolve: {
                    modalData: $ctrl.dataForModal
                }
            }).result.then(function(result) {
                console.info("I was closed, so do what I need to do myContent's controller now and result was->");
                console.info(result);
            }, function(reason) {
                console.info("I was dimissed, so do what I need to do myContent's controller now and reason was->" + reason);
            });
        };
    }
});

angular.module('app')
.component('myModal', {
    template: `<div class="modal-body"><div>{{$ctrl.greeting}}</div> 
<label>Name To Edit</label> <input ng-model="$ctrl.modalData.name"><br>
<label>Value To Edit</label> <input ng-model="$ctrl.modalData.value"><br>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleClose()">Close Modal</button>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleDismiss()">Dimiss Modal</button>
</div>`,
    bindings: {
        $close: '&',
        $dismiss: '&',
        greeting: '<',
        modalData: '<'
    },
    controller: [function() {
        var $ctrl = this;
        $ctrl.handleClose = function() {
            console.info("in handle close");
            $ctrl.$close({
                result: $ctrl.modalData
            });
        };
        $ctrl.handleDismiss = function() {
            console.info("in handle dismiss");
            $ctrl.$dismiss({
                reason: 'cancel'
            });
        };
    }],
});
PCalouche
  • 1,605
  • 2
  • 16
  • 19
  • 6
    Nicest solution I've found. Plunk made it easy to repurpose. Thank you. – Gary Wisniewski Aug 05 '16 at 05:22
  • Thanks, I was missing the promise handler – luis19mx Jan 20 '17 at 01:32
  • This is a great answer. I was using an old version of ui-bootstrap with template+controllers and didn't know about the support for components. The Plunk was very helpful and it works a treat. Thanks. – giles123 Feb 01 '17 at 12:59
  • This is the best answer I've found, works like a charm! I just have one question though, why is it that you use '.result.then(func..' as the promise handler instead of just '.result(func..' or '.then(func..'? – Backer Feb 16 '17 at 10:26
  • That is part of UI Bootstraps API. Result is of type promise and that is explained when they discuss the open method here http://angular-ui.github.io/bootstrap/#!#modal – PCalouche Feb 17 '17 at 12:10
  • 1
    Think your updated code has an error - should be `$ctrl.$onInit` instead of `$ctrl.$init`. – Vedran May 18 '18 at 17:49
  • You are correct @Vedran. I have updated the code. Thank you. – PCalouche May 18 '18 at 20:45
12

There is no need to make it more complicated by passing along the parent controller, you can just access it from within the .component that displays the modal.

Component

/**
 * @ngdoc component
 * @name fsad.component:video
 *
 * @description <fsad-video> component, in development...
 *
 */


(function () {
  'use strict';

  angular.module('fsad').component('fsadVideo', {
    bindings: {},
    templateUrl: function(appConstant){return appConstant.paths.modules.fsad + 'leefloon/fsad-video.html'},
    controller: controller
  });

  controller.$inject = ['$scope'];
  function controller($scope){

    var $ctrl = this;

    setDataModel();

    /****************************************************************/

    $ctrl.ui.close = close;

    /****************************************************************/

    function setDataModel(){

      $ctrl.ui = {};

    }
    function close(){
      $scope.$parent.$close();
    }

  }

}());

Opening the modal

  var modalInstance = $uibModal.open({
    backdrop: 'static',
    keyboard: true,
    backdropClick: false,
    template: '<fsad-video></fsad-video>',
    windowClass: 'edit-contactenblad',
  });

Since you are stating that the template is a component, the $scope.$parent will always be pointing to the modal instance. Allowing you to access the $close() function.

Passing and receiving data

If you need to pass data to the component, or receive data back from the component, you can do it like this.

  var modalInstance = $uibModal.open({
    backdrop: 'static',
    keyboard: true,
    backdropClick: false,
    template: '<fsad-video method="$ctrl.method" on-viewed="$ctrl.userHasViewedVideo(time)"></fsad-ideo>',
    controller: function(){
      this.method = method;
      this.userHasViewedVideo = function(time){}
    },
    controllerAs: '$ctrl',
    windowClass: 'edit-medewerker',
  });

Just on a side note, i'm using this structure style guide to create the component.

kevinius
  • 4,232
  • 7
  • 48
  • 79
6

If you want access to the $uibModal's $close() and $dismiss() functions, along with some parent data and function binding within your component, you can pass them all along as such:

Open Modal Logic

$uibModal.open({
    template: '<login close="$close()" dismiss="$dismiss()" ' +
        'email="$ctrl.cookieEmail" check-login="$ctrl.ajaxLogin(user, pass)"></login>',
    controller: function () {
        this.cookieEmail = $cookies.get('savedEmail');
        this.ajaxLogin = AjaxLoginService.login;
    },
    controllerAs: '$ctrl'
});

Modal Login Component

{
    templateUrl: 'view/login.html',
    bindings: {
        email: '<',
        checkLogin: '&',
        close: '&',
        dismiss: '&'
    },
    controller: function () {
        var viewModel = this;

        viewModel.password = '';

        viewModel.submit = function () {
            viewModel.checkLogin(
                { user: viewModel.email, pass: viewModel.password }
            ).then(function (success) {
                viewModel.close();
            });
        }
    }
}

Modal HTML

<form ng-submit="$ctrl.submit()">
    <input type="text" ng-model="$ctrl.email" />
    <input type="password" ng-model="$ctrl.password" />
    <button type="button" ng-click="$ctrl.dismiss()">Cancel</button>
    <button type="submit">Login</button>
</form>

The AngularJS 1.5 docs are a little sparse, but they show the usage of the & binding as a function wrapper: https://docs.angularjs.org/guide/component

Matt Janssen
  • 1,505
  • 13
  • 14
3

You need to pass the parent controller to the modal component with the modal instance on it. To do that you need to append the generate HTML of the modal in the parent component

parent component

$ctrl.openModal = function(){
    $ctrl.modalInstance = $uibModal.open({
        template: '<your-modal></your-modal>',
        appendTo : $document.find('parentComponent')
    });
}

modal component

.component('yourModal', {
        templateUrl: 'path/to/modal.html',
        replace: true,
        require: {
            parent : '^parentComponent'
        },
        controller: ModalCtrl
    });

function ModalCtrl() {
    var $ctrl = this;

    $ctrl.$onInit = function(){

        var instance = $ctrl.parent.modalInstance;

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

        $ctrl.selected = {
            item: $ctrl.items[0]
        };

        $ctrl.ok = function () {
            instance.close($ctrl.selected);
        };

        $ctrl.cancel = function () {
            instance.dismiss('cancel');
        };

        instance.result.then(function (selectedItem) {
            $ctrl.selected = selectedItem;
        }, function () {
            console.log('Modal dismissed at: ' + new Date());
        });
    };


}

Be carefull because the required controller will only be available after the $onInit.

Renato Machado
  • 2,270
  • 1
  • 10
  • 7
  • Thank you for your answer. But it seems not working well on my environment. How should I do when I want to call function which is defined in `$ctrl.$onInit` like `$ctrl.ok` and `$ctrl.cancel` from template? I tried to call that by following way `` . – wtadahiro Apr 22 '16 at 02:22
  • Oh, I'm sorry I made a mistake. Finally It seems working well. Thank you so much. – wtadahiro Apr 22 '16 at 02:25