0

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?

user2024621
  • 275
  • 3
  • 9
  • Controllers are meant for that exact purpose. To Bind data to your view. I dont understand why you dont want to use them. Using a controller to create a scope and using a service to share the data would been the "Angular way" of doing this. – ganaraj Mar 25 '13 at 10:28

2 Answers2

0

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

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Thank you charlietfl, that's nice! In the angularjs zen, is it better to always try to have a parent ctrl that setup a shared scope? I realize that if I have multiple times this pattern, I have to construct a lot of services just to share my data, and then also to keep a long list of event... :-( – user2024621 Mar 25 '13 at 17:05
  • Alternatively could we setup a view in the service like this: `var myView = null`and a method `setView = function(element){` that `aCtrl` will then bind with a DOM element on dom ready or it is a bad idea? – user2024621 Mar 25 '13 at 17:09
  • defintiely easier having a parent controller , but a lot depends on depth of your application – charlietfl Mar 25 '13 at 17:21
0

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

DrunkenBeetle
  • 267
  • 4
  • 2