Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm wondering what could be a good way to share directive between controller. I've got ie two directives to use in different controller with different configuration the first think I thought of using like:

//html
<body data-ng-controller="MainCtrl">
    <div class="container">
        <div data-ui-view></div>
    </div>
</body>

//js
.controller('MainCtrl', function ($scope,$upload) {
    /*File upload config*/
    $scope.onFileSelect = function($files) {
        for (var i = 0; i < $files.length; i++) {
          var file = $files[i];
          $scope.upload = $upload.upload({
                url: 'server/upload/url', 
                method: 'POST',
                data: {myObj: $scope.myModelObj},
                file: file,
          }).progress(function(evt) {
            console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
          }).success(function(data, status, headers, config) {

            console.log(data);
          });

        }
    };
    /* Datepicker config */
    $scope.showWeeks = true;
    $scope.minDate = new Date();
    $scope.open = function($event) {
        $event.preventDefault();
        $event.stopPropagation();
        $scope.opened = true;
    };
    $scope.dateOptions = {
        'year-format': "'yy'",
        'starting-day': 1
    };
    $scope.format = 'MMM d, yyyy';
})
.controller('IndexCtrl', function ($scope) {

})

doing so I can use all the functions in my children controller but I don't like very much because of collision problems. Since you cannot use a service (you can't use $scope in a service) the other alternatives could be make an other directive or put the code in a run block but it's quite the same using a parent controller so what do you think about ?

UPDATE

what do you think about this approach ?

//outside of angular stauff
function MyTest(){
    this.testScope = function(){
        console.log('It works');
    }
}

//inside a controller
$scope.ns = new MyTest();

//in the view
<p ng-click="ns.testScope()">ppp</p>

RIUPDATE this seems the best option :)

MyTest.call($scope);
share|improve this question

4 Answers 4

up vote 3 down vote accepted

Consider the method described by this post: Extending AngularJS Controllers Using the Mixin Pattern

Instead of copying your methods out of a service, create a base controller that contains those methods, and then call extend on your derived controllers to mix them in. The example from the post:

function AnimalController($scope, vocalization, color, runSpeed) {

    var _this = this;

    // Mixin instance properties.
    this.vocalization = vocalization;
    this.runSpeed = runSpeed;

    // Mixin instance methods.
    this.vocalize = function () {
        console.log(this.vocalization);
    };

    // Mixin scope properties.
    $scope.color = color;

    // Mixin scope methods.
    $scope.run = function(){
        console.log("run speed: " + _this.runSpeed );
    };
}

Now we can mixin AnimalController into DogController:

function DogController($scope) {

    var _this = this;

    // Mixin Animal functionality into Dog.
    angular.extend(this, new AnimalController($scope, 'BARK BARK!', 'solid black', '35mph'));

    $scope.bark = function () {
        _this.vocalize(); // inherited from mixin.
    }
}

And then use DogController in our template:

<section ng-controller="DogController">
    <p>Dog</p>

    <!-- Scope property mixin, displays: 'color: solid black' -->
    <p ng-bind-template="color: {{ color }}"></p>

    <!-- Calls an instance method mixin, outputs: 'BARK BARK!' -->
    <button class="btn" ng-click="bark()">Bark Dog</button>

    <!-- Scope method mixin, outputs: 'run speed: 35mph' -->
    <button class="btn" ng-click="run()">Run Dog</button>
</section>

The controllers in this example are all in the global space and are included in the markup as follows.

<script type="text/javascript" src="lib/jquery.js"></script>
<script type="text/javascript" src="lib/angular.js"></script>
<script type="text/javascript" src="app/controllers/animal-controller.js"></script>
<script type="text/javascript" src="app/controllers/dog-controller.js"></script>
<script type="text/javascript" src="app/controllers/cat-controller.js"></script>
<script type="text/javascript" src="app/app.js"></script>

I haven't tested it, but I don't see why the following wouldn't work:

var myApp = angular.module('myApp', [])

.controller('AnimalController', ['$scope', 'vocalization', 'color', 'runSpeed', function ($scope, vocalization, color, runSpeed) { /* controller code here */}]);

.controller('DogController', ['$scope', '$controller', function($scope, $controller) {
    var _this = this;

    // Mixin Animal functionality into Dog.
    angular.extend(this, $controller('AnimalController', {
         $scope: scope,
         vocalization: 'BARK BARK!', 
         color: 'solid black', 
         runSpeed:'35mph' 
    }));

    $scope.bark = function () {
        _this.vocalize(); // inherited from mixin.
    }
}]);

see: docs for $controller service

share|improve this answer
    
It's just what I'm looking for THANKS :) –  Whisher Jan 30 '14 at 19:43
    
On second thoughts how to do that without using globally-defined controller definition ? –  Whisher Jan 30 '14 at 21:44
    
in the example repo they are globally defined github.com/bparvizi/angular-js-controller-mixins. But there's no reason they have to be -- consider how controllers can be required in combination with browserify: ethanway.com/angular-and-browserify -- but yes, the base controller has to be defined at the point of mixin. –  marfarma Jan 30 '14 at 22:17
    
updated with s version using the $controller service to create the base controller. –  marfarma Jan 30 '14 at 22:56
    
I added just an other reply ^^ –  Whisher Jan 31 '14 at 19:48

What you want is terrible.

You wouldn't want your controllers to know anything about each other, let alone, one having access to the function of the other. You can just use a Service to achieve that. As for using directives, not sure what exactly you want to happen.

As for your second thing, you can as easily do this

.service('MyTestService', function(){
    return {
       testScope: function(){
           console.log('It works');
       }
    };
})

.controller('MyController', ['$scope', 'MyTestService', function($scope, MyTestService){
   $scope.testScope = MyTestService.testScope;
}])

and in your view:

<p ng-click="testScope()">ppp</p>
share|improve this answer
    
Thanks for the point.What about MyTest.call($scope); it seems quite neat as solution ... –  Whisher Jan 29 '14 at 10:35
1  
I am quite fidgety about anything that includes globally scoped objects, but yes, it would work, as long as you get $scope to be the correct thingy. You could use that pattern for smoke testing purposes but I wouldn't use it in production code. –  baba Jan 29 '14 at 10:52
    
I added a reply –  Whisher Jan 30 '14 at 15:44

I ended up with:

//service
.service('PostUploader',function($upload){
        var that = this;
        var fileReaderSupported = window.FileReader !== null;
        this.notify = null;
        this.success = null;
        this.showAlert = false;
        this.avatar = '';
        this.onFileSelect = function($files) {
            var $file = $files[0];
            var filename = $file.name;
            this.avatar = filename;
            var isImage = /\.(jpeg|jpg|gif|png)$/i.test(filename);
            if(!isImage){
                this.showAlert = true;
                return;
            }
            this.showAlert = false;
            if (fileReaderSupported && $file.type.indexOf('image') > -1) {
                var fileReader = new FileReader();
                fileReader.readAsDataURL($file);
                fileReader.onload = that.notify;
            }
            $upload.upload({
                url :'/api/post/upload',
        method: 'POST',
        headers: {'x-ng-file-upload': 'nodeblog'},
        data :null,
        file: $file,
        fileFormDataName: 'avatar'
            })
            .success(that.success)
            .progress(function(evt) {

            })
            .error(function(data, status, headers, config) {
                throw new Error('Upload error status: '+status);
            })

        };
        this.closeAlert = function() {
            this.showAlert = false;
        };
    })    

//controller
 /* Uploader post */
        $scope.dataUrl = null;
        $scope.avatar = PostUploader.avatar;
        $scope.showAlert = PostUploader.showAlert;
        $scope.onFileSelect = PostUploader.onFileSelect;
        $scope.closeAlert = PostUploader.closeAlert;
        PostUploader.notify = function(e){
            $timeout(function() {
                $scope.dataUrl = e.target.result;
            });
        };
        PostUploader.success = function(data, status, headers, config) {
           $timeout(function() {
                $scope.post.avatar = data.url;
            });
        }
        $scope.$watch('avatar',function(newVal, oldVal){
            if(newVal) { 
                $scope.avatar = newVal;
            }  
        }); 
        $scope.$watch('showAlert',function(newVal, oldVal){
            $scope.showAlert = newVal;
            $scope.dataUrl = null;
        }); 

I did so because I've to do the same thing in create post and edit post but all in all I've got quite the same repeated code ! :)

The only good thing is the code has got less logic.

share|improve this answer

obvious but brilliant solution (may be)

(function(window, angular, undefined) {
                'use strict';
                angular.module('ctrl.parent', [])
                    .run(function ($rootScope) {
                        $rootScope.test = 'My test'   
                        $rootScope.myTest = function(){
                            alert('It works');
                        }
                });
            })(window, angular);
            angular.module('app',['ctrl.parent'])
                .controller('ChildCtrl', function($scope){

            });

It's easy and clean and don't see any drawback(it's not global)

UPDATE

'use strict';
                (function(window, angular, undefined) {
                    'use strict';
                    angular.module('ctrl.parent', [])
                        .controller('ParentController',function (scope) {
                            scope.vocalization = '';
                            scope.vocalize = function () {
                                console.log(scope.vocalization);
                            };
                    });
                })(window, angular);
                angular.module('app',['ctrl.parent'])
                    .controller('ChildCtrl', function($scope,$controller){
                    angular.extend($scope, new $controller('ParentController', {scope:$scope}));
$scope.vocalization = 'CIP CIP';
                });

just a little neater and it works CIP CIP :)

share|improve this answer
    
The drawback is that you're using $rootScope as your parent controller - what happens if you want more than one parent controller? It would be cleaner if you define your parent controllers in an angular module, as controllers, and then instantiate it using the $controller service. I'll update my answer with an example so you can see what I mean. –  marfarma Jan 31 '14 at 20:12
    
I've updated again :) thanks for the point. What do you think ? I just simplify your code –  Whisher Jan 31 '14 at 20:36
    
I've credited you stackoverflow.com/questions/21464810/… –  Whisher Jan 31 '14 at 20:42

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.