سؤال

I'm trying to create a directive for bootstrap-select:

http://silviomoreto.github.io/bootstrap-select/ (it allows creating nice looking selects)

according to the following tutorial it should be an easy task to convert jquery plugin into angular directive: http://www.youtube.com/watch?v=9Bu73oQQSio But in fact it isn't at current point I'm stuck on my first step, I'm unable to get the most basic functionality working. I've tried different approaches:

<select multiple selectpicker>
  <option selectpickeroption ng-repeat="MyModel in MyModels">
    {{ MyModel.MyProperty }}
  </option>
</select>

<selectpicker multiple="multiple">
    <option ng-repeat="MyModel in MyModels">
        {{ MyModel.MyProperty }}
    </option>
</selectpicker>

together with

Application.directive('selectpicker', [function () {
    return {
        restrict: 'A',
        compile: function() {
            return function(scope, element, attributes, controller) {
                element.selectpicker(); // launch `.selectpicker()` plugin
                // Lunching selectpicker at current point is way to early,
                // at this point all <option ng-repeat="MyModel in MyModels">..</option>
                // contain the following text `{{ MyModel.MyProperty }}`
            };
        }
    };
}]);

But none of them worked, It just don't want to integrate into Angular. Is there a simple way of wrapping this simplistic plugin into angular with minimal effort? Because I've spent half day trying to make it work

:(

هل كانت مفيدة؟

المحلول

This is an issue of timing - your selectpicker directive is running before the <option> elements have been created.

To get around this you need to delay your call to element.selectpicker() until the <option> elements have been created.

Response to comments:

@Lu4 #1: You're right, generally in Angular it's possible to avoid using $timeout. I've updated the answer to use $watch instead (if you're using Angular 1.2+ then you'll probably want to use $watchCollection).

@dluz #2: As far as I'm aware the $timeout is reliable enough for this situation, however the updated answer should address any concerns about that.

Using document.ready() isn't appropriate here because that relates to the browser's state of the loaded page, not how Angular works with the DOM. Because our Angular code is already running know that the ready event has already fired - document.ready() has to fire before Angular can run.

The issue that Lu4 has encountered is to do with how Angular processes directives - it operates on each element from the outside in, initialising all directives before moving onto the child elements and their directives (for simplicity we're ignoring terminal directives). So when the selectpicker directive is invoked Angular hasn't processed the child elements yet.

Updated Answer:

Update your HTML to pass MyModels into the selectpicker directive as the collection you want to watch.

<select multiple selectpicker="MyModels">
    ...
</select>

Then update the directive to set up a $watch on the value of the selectpicker attribute.

From my quick tests the $watch will fire once the DOM has been updated and you should be able to call selectpicker().

myApp.directive('selectpicker', [
    return {
        function(){
            restrict: 'A',
            scope: true,
            link: function(scope, element, iAttrs, controller){
                console.log('selectpicker::link: element=', element.eq(0).children().length);
                
                scope.$watchCollection(iAttrs['selectpicker'], function(newValue){
                    console.log('selectpicker::$watch: element=',  element.eq(0).children().length);
                    element.selectpicker();
                });
            }
        };
    }]);

With this approach the $watch will fire when the collection is set on the scope or if the collection is replaced. In Angular 1.2 you should use $watchCollection so you can be notified if the number of items in the array change.


Orignal Answer:

To get around this you need to use the $timeout service to delay your call to element.selectpicker() until the next "tick".

myApp.directive('selectpicker', ['$timeout',
    function($timeout){
        return {
            restrict: 'A',
            link: function(scope, element, iAttrs, controller){
                // Log the number of children of the <select>.
                // This will be 0 because Angular hasn't processed the children yet.
                console.log('selectpicker::link: element=', element.eq(0).children().length);
                
                var initSelectpicker = function(){
                    // Now the children have been created.
                    console.log('selectpicker::init: element=', element.eq(0).children().length);
                    
                    element.selectpicker();
                }
                
                $timeout(initSelectpicker, 0, false);
            }
        };
    }]);

That should get things started but you're going to have to be careful - if MyModels changes then you're going to have to make sure that the selectpicker is kept up to date as well.

نصائح أخرى

You need to call the plugin using the link: function of the directive. You only will be able to register any listeners on the elements and set up any watches with the scope during the link phase. (not the compile phase)

If you take a closer look at the YouTube video tutorial you mentioned, he is doing everything inside the link: function.

 myApp.directive('selectpicker', [function () {
        return {
            restrict: 'A',
            link: function (scope, element, iAttrs, controller) { 

              console.log(element);
              element.selectpicker(); 

            }

        };
    }]);

As a rule you will only need to use the compile phase if you need to transform/change the template's DOM element. In the other hand the link function will allow the directive to register listeners on the specific cloned DOM element instance as well as to copy content into the DOM from the scope.

You can find out more here - http://docs.angularjs.org/guide/directive

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top