0

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);

4 Answers 4

7

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

5
  • It's just what I'm looking for THANKS :)
    – Whisher
    Commented Jan 30, 2014 at 19:43
  • On second thoughts how to do that without using globally-defined controller definition ?
    – Whisher
    Commented Jan 30, 2014 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. Commented Jan 30, 2014 at 22:17
  • updated with s version using the $controller service to create the base controller. Commented Jan 30, 2014 at 22:56
  • I added just an other reply ^^
    – Whisher
    Commented Jan 31, 2014 at 19:48
3

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>
2
  • Thanks for the point.What about MyTest.call($scope); it seems quite neat as solution ...
    – Whisher
    Commented Jan 29, 2014 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. Commented Jan 29, 2014 at 10:52
0

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.

0

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 :)

3
  • 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. Commented Jan 31, 2014 at 20:12
  • I've updated again :) thanks for the point. What do you think ? I just simplify your code
    – Whisher
    Commented Jan 31, 2014 at 20:36
  • I've credited you stackoverflow.com/questions/21464810/…
    – Whisher
    Commented Jan 31, 2014 at 20:42

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.