0

I've created an angular app that has the following structure. Application configuration, routes, directives, controllers and filters are all defined in index.js (I know this is not recommended). All of my general functions are in a controller called main.js, this is also the controller I am using in my main view in index.html. From then on the app consists of 10 different views, each has it's own controller.

main.js has become very difficult to maintain, so I would like to separate it into five external "utility" style files that contain the general function the application uses. These functions all use angular's $scope and must be able to be accessed by all the views and controllers that exist in the application.

For the past few days I've tried several different methods, such as defining the functions under angular's factory service, using angular's $provide method, defining a controller without a view and many others. None of them worked for me. What is the simplest way to separate the functions that exist in main.js to external js files without changing any code within the functions themselves. Let's pretend that the function cannot be turned into a directive.

Example - Function that checks users name for 'guest' string and returns an image

main.js -

$scope.defaultpic = function(username) {
        var guest = username;
        if (guest.indexOf("guest") != -1){
            {return {"background-image": "url('data:image/png;base64,chars"}}
        }
    }

in the view

<img ng-style="defaultpic(JSON.Value)" class="user_pic" ng-src="getprofilepic/{{JSON.Value}}"/>

Cheers, Gidon

5
  • Can you add an example of a utility function that needs to access the scope? If it's really a utility function, it should be possible to refactor it to not use the $scope. Otherwise it might be a candidate for a directive. Anyway, hard to say without an example. Commented Oct 21, 2013 at 12:41
  • Hi @Narretz, added an example. Please do not provide me with a way to refactor the code or convert it to a directive, I know how to do these methods, that is also not my question. I would like to know how to move the function to a seperate js file. tnx Commented Oct 21, 2013 at 12:54
  • Well since you don't want any hints, I will just say that you use the controller in a way that is not angular "best practice". You could write your example completely in the view or use a directive, or even put the function in a service, and bind the service function to a $scope variable. I don't recommend it, though. Commented Oct 21, 2013 at 12:59
  • Thanks for the answers @Narretz :) I tried to put the function in a service, but the $scope wasn't recognized. Could you please provide an example or point me to one that includes the binding to a $scope variable? and why don't you recommend it? Commented Oct 21, 2013 at 13:14
  • I don't recommend it because it is essentially and if-else for a style change, which for me is DOM manipulation, and controllers and services should as far as possible only be concerned with business logic. Commented Oct 21, 2013 at 14:22

2 Answers 2

1

In order to use the function in markup, you still have to bind it to the scope. But, you can move the body of the function to a service:

angular.module('myapp').factory('picService',[ function () {
    return {
        defaultpic: function(username) {
            var guest = username;
            if (guest.indexOf("guest") != -1){
                {return {"background-image": "url('data:image/png;base64,chars"}}
            }
        }
    };
}]);

And then bind it up in the controller:

$scope.defaultpic = picService.defaultpic;
Sign up to request clarification or add additional context in comments.

1 Comment

Great answer Davin! I made a more detailed one in case anyone viewing this answer needs one. I hope it helps ^_^
1

Refactor controller functions as services declared in different files

As you correctly stated, a great approach to refactor the functions is to put them into different services.

According to the angular Service docs:

Angular services are singletons objects or functions that carry out specific tasks common to web apps.

Here is an example:

Original code

Here we have a simple Hello World app, with a controller that has two functions: greet() and getName().

app.js

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.getName = function () {
    return 'World';
  }

  $scope.greet = function (name) {
    return 'Hello ' + name;
  };
});

index.html

...
<div id="container" ng-controller="MainCtrl">
  <h1>{{greet(getName())}}</h1>
</div>
...

We want to test that our scope always has both functions, so we know it is working as intended, so we are going to write two simple jasmine tests:

appSpec.js

describe('Testing a Hello World controller', function() {
  var $scope = null;
  var ctrl = null;

  //you need to indicate your module in a test
  beforeEach(module('plunker'));

  beforeEach(inject(function($rootScope, $controller) {
    $scope = $rootScope.$new();

    ctrl = $controller('MainCtrl', {
      $scope: $scope
    });
  }));

  it('should say hallo to the World', function() {
    expect($scope.getName()).toEqual('World');
  });

  it('shuld greet the correct person', function () {
    expect($scope.greet('Jon Snow')).toEqual('Hello Jon Snow');
  })
});

Check it out in plnkr

Step 1: Refactor controller functions into separate functions

In order to start decoupling our controller to our functions we are going to make two individual functions inside app.js.

app.js

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.getName = getName;

  $scope.greet = greet;
});

function getName() {
  return 'World';
}

function greet(name) {
  return 'Hello ' + name;
}

Now we check our test output and we see that everything is working perfectly.

Check out the plnkr for step 1

Step 2: Move functions to their own services

We will define a NameService and GreetService, put our functions in them and then define the services as dependencies in our controller.

app.js

var app = angular.module('plunker', []);

app.service('NameService', function () {
  this.getName = function getName() {
    return 'World';
  };
});

app.service('GreetService', function() {
  this.greet = function greet(name) {
    return 'Hello ' + name;
  }
});

app.controller('MainCtrl', ['$scope', 'NameService', 'GreetService', function($scope, NameService, GreetService) {
  $scope.getName = NameService.getName;

  $scope.greet = GreetService.greet;
}]);

We make sure that our tests are still green, so we can move on to the final step.

Have a look at step 2 in plunker

Final Step: Put our services in different files

Finally we will make two files, NameService.js and GreetService.js and put our services in them.

NameService.js

angular.module('plunker').service('NameService', function () {
  this.getName = function getName() {
    return 'World';
  };
});

GreetService.js

angular.module('plunker').service('GreetService', function() {
  this.greet = function greet(name) {
    return 'Hello ' + name;
  }
});

We also need to make sure to add the new scripts to our index.html

index.html

...
<script src="NameService.js"></script>
<script src="GreetService.js"></script>
...

This is how our controller looks like now, neat huh?

app.js

var app = angular.module('plunker', []);

app.controller('MainCtrl', ['$scope', 'NameService', 'GreetService', function($scope, NameService, GreetService) {
  $scope.getName = NameService.getName;

  $scope.greet = GreetService.greet;
}]);

Plunker for the final step.

And that's it! Our tests still pass, so we know everything works like a charm.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.