Question

Let's imagining that I have a directive that contains some data and I want to interpolate it somewhere else in a second component (directive or else), without having a controller linking them.

For example, take the zippy of angularjs webpage, but instead of having <div ng-controller="Ctrl3"> bidding the data from the input to the zippy directive, we have two separate components:

    <!-- first component with data -->
    <div ng-controller="aCtrl">

    Title: <input ng-model="title"> <br>
    Text: <textarea ng-model="text"></textarea>
    </div>

    <hr>
    <!-- a second component wanting to show the data -->
    <div class="zippy" zippy-title="Details: {{title}}...">{{text}}</div>

My question is then how can I link them nicely in Angularjs?

I tried to pass through a service, registering the data changes, and then tried unsuccessfully to bind it with my DOM through an injection into a directive or through a controller.

( I want to display data contained in a directive in an another directive "window", but I don't want to wrap all my code with a controller just to bind the data)

Is there a way to do it nicely?

Was it helpful?

Solution

Here is one solution using a service that triggers a custom event on $rootScope, then listens for event in controller of directive'

app.factory('SharedData',function($rootScope){
    return{
        data:{text : 'World', title:'Foo bar'},
        upDate:function(prop,val){
           this.data[prop]=val;
           $rootScope.$emit('dataUpdate',prop)
        }
    }
});


app.controller('aCtrl', function ($scope,SharedData) {
    angular.forEach(SharedData.data,function(value,key){
        $scope[key] = value;
        $scope.$watch(key,function(){
            SharedData.upDate(key,$scope[key]);
        });
    });
});

app.directive('zippy',function($rootScope,SharedData){
    return{
        restrict:'C',
        replace:false,
        scope:{},
        controller:function($scope) {
            $rootScope.$on('dataUpdate',function(event,prop) {
                $scope[prop] = SharedData.data[prop];
            });
        }
    }
});

Plunker Demo

OTHER TIPS

There is another option besides emitting events or wrapping directives into a parent controller (nothing wrong with those options btw). The other option is to have a generic service/factory where you can register arbitrary directive controllers, and then use those registered controllers in other related or non related directives.

Below has a service called directiveCommunicator where you can get, set and unset directive controllers (you can use a factory if you want, its just my preference to use services). We then have another 2 directives called foo and bar, which the foo directives registers its controller to be used, which in turn is used by the bar directive. Note that foo and bar directives are not parent/child related

// Service used to register/use any arbitrary controller
app.service('directiveCommunicator',
    function()
    {
        var _controllers = {};

        this.get =
            function(id)
            {
                if (!(id in _controllers)) {
                    return null;
                }

                return _controllers[id];
            };

        this.set =
            function(id, controller)
            {
                _controllers[id] = controller;
            };

        this.unset =
            function(id)
            {
                if (!(id in _controllers)) {
                    return;
                }

                delete _controllers[i];
            }
    }
);

app.directive('foo',
    [
        'directiveCommunicator',
        function(directiveCommunicator)
        {
            return {
                'restrict': 'A',
                'scope':
                    {
                        'colour': '='
                    },
                'controller':
                    function($scope)
                    {
                        // We register out controller with a unique ID so we can use it in other directives
                        directiveCommunicator.set('colourBox', this);

                        // We also unregister it once we get destroyed, otherwise we'll be leaking memory
                        $scope.$on('$destroy',
                            function()
                            {
                                directiveCommunicator.unset('colourBox');
                            }
                        );

                        this.changeColour =
                            function(colour)
                            {
                                $scope.$apply(
                                    function()
                                    {
                                        $scope._colour = colour;
                                    }
                                );
                            }
                    },
                'link':
                    function($scope, $element, $attr)
                    {
                        $scope._colour = $attr.colour;

                        $scope.$watch('_colour',
                            function()
                            {
                                $element.attr('class', $scope._colour);
                            }
                        );
                    }
            }
        }
    ]
);

app.directive('bar',
    [
        'directiveCommunicator',
        function(directiveCommunicator)
        {
            return {
                'restrict': 'A',
                'scope':
                    {
                        'colour': '='
                    },
                'link':
                    function($scope, $element, $attr)
                    {
                        $element.text($attr.colour);

                        $element.bind('click',
                            function()
                            {
                                // We get the registered controller and call the 'changeColour' method on it
                                var ctrl = directiveCommunicator.get('colourBox');

                                ctrl.changeColour($attr.colour);
                            }
                        );
                    }
            }
        }
    ]
);

I've made a little Plunker demo to see foo and bar in action. The foo directive is just a little square div where you can change the background in the associated controller. bar is another non related directive that will call foo's controller method changeColour and change the colour based on the supplied attributes.

Haven't used this setup in production yet and you also need to handle unregistering controllers, but should work. You could also have a 3rd argument in directiveCommunicator.set method being the controller's scope, which the method will automatically add in the $destroy/unregister setup, therefore not having to call directiveCommunicator.unset anymore

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top