Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Still confused about angularjs services and factories

I've read several threads about angularjs services and factories. I understand that services are singletons and factories return instances of objects. But I still don't really understand how to use them. My app is a simple social network. The person using the app needs to log in, then they can view other members and send them messages.

My ideal design would be:

  1. Create a Member object to represent an arbitrary member of my service. Whenever a 'list' or 'get' operation returns data, it'd be wrapped in this member so I could call utility methods on it. I'd use a factory for this.
  2. When a user logs in to the app, create a Member to represent them (containing their userID and auth token for future authenticated requests). This must be available to other scopes, so could either be attached to the $rootScope, or be a MemberService that returns an instance of a Member tailored to the authenticated user.

I have created the following:

angular.module('myapp.models', [])
.factory('Member', ['$log', 'DataService', '$http', 'Restangular',
    function($log, DataService, $http, Restangular) {
        return {
            user_id: null,
            first_name: null,
            last_name: null,

            authenticate: function(username, password, loginSuccessHandler, loginErrorHandler) {
                $log.debug("inside Member.authenticate");

                var authSuccessHandler = function(data, status, headers, config) {
                    $http.defaults.headers.common.Authorization = 'Basic ' + btoa(data._id + ':' + data.token);
                    var token = btoa(data._id + ':' + data.token);
                    user_id = data._id;       // attach this to the rootScope? Needs to be
                                              // globally accessible (or even this whole object)
                    Restangular.setDefaultHeaders({'Authorization': 'Basic ' + token});
                    $log.debug("Auth successful, token stored " + token);
                    loginSuccessHandler();
                };

                DataService.authenticate(username, password, authSuccessHandler, authErrorHandler);
            },
      ...

How can I instantiate this and make it available to other scopes (e.g. so in other scopes I'd know the logged in user's ID)?

Also, how could I instantiate this object when I'm parsing a list of members? E.g. if I have an object {first_name: "John", last_name: "Smith"} how could I get a Member object from this factory with these attributes set?

like image 630
jbrown Avatar asked Feb 13 '23 05:02

jbrown


1 Answers

Both factory and service are abstractions over provider.

This is the method used by angular to instantiate new providers:

function provider(name, provider_) {
    if (isFunction(provider_) || isArray(provider_)) {
        provider_ = providerInjector.instantiate(provider_);
    }
    if (!provider_.$get) {
        throw Error('Provider ' + name + ' must define $get factory method.');
    }
    return providerCache[name + providerSuffix] = provider_;
}

the first part instantiates a new (singleton) object when the angular app loads up. The $get method is used as a constructor for new objects. This is how an instantiation of a provider might look like (from angular's docs):

myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
  var useTinfoilShielding = false;

  this.useTinfoilShielding = function(value) {
    useTinfoilShielding = !!value;
  };

  this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {

    // let's assume that the UnicornLauncher constructor was also changed to
    // accept and use the useTinfoilShielding argument
    return new UnicornLauncher(apiToken, useTinfoilShielding);
  }];
});

Here's how you configure the provider. in the module.config() block, unlike when we inject the provider to a controller/directive/service later on, we get the singleton, rather than the return value of the $get method.

myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
  unicornLauncherProvider.useTinfoilShielding(true);
}]);

Then, whenever you inject and call unicornLauncher, what's in the $get method will get called and you will get a new UnicornLauncher with the supplied configuration, useTinfoilShielding = true in this case.

As you can see, the provider has two parts. First, it's a singleton, instantiated and configured when angular loads up, and pushed to the providerCache, so you can use it throughout the application. It also has a $get method used to instantiate new objects.

You can look at it like this: The singleton has its attributes and methods as settings and data that you set when you load the app, and then you can create new objects that use those settings and data.

If you create a module for other people to use, that needs to be modified with options for each application individually, this is how you would do it. You would inject the provider to your app, configure it, then have it dish out objects as you need them, with the presets we set when we configure the provider.

Then we have service and factory. These are actually abstractions over provider. Here is the function that angular uses to start a factory:

function factory(name, factoryFn) {
    return provider(name, { $get: factoryFn });
}

It's merely a provider, but rather than returning an object with all those attributes we saw, it returns an object with just a $get method and doesn't have any other, mutable parts like the provider.

Here is the function for service:

function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
        return $injector.instantiate(constructor);
    }]);
}

Here we have a factory whose $get method returns a singleton, instantiated by angular, using the provided constructor. So you have a singleton that you can use throughout your app, you can use its methods everywhere. You can set its attributes in one place and see the same values you set everywhere else in the app, since it's the same, singleton object wherever you inject it.

Summary:

The main difference is that a service is an object created by angular from a constructor you give it when you declare the service, and factory is merely a function that you can use to create new objects, just like you would in native JavaScript.

Hope that helps understand factory and service.


Back to your question.

In your case, you have a factory that you want to use to create a new object for each member. In a controller where you inject Member, you could simply do:

var data = {first_name: "John", last_name: "Smith"} 
var member = new Member(data);

With a small change to your factory:

.factory('Member', ['$log', 'DataService', '$http', 'Restangular',
    function($log, DataService, $http, Restangular) {
        return function(data) {
        //construct the object
        }
])

The new call to the factory (injected as the constructor) would return a new, unique object, constructed just like any other in JS. You can do whatever you want with it. You can leave it in the controller, in $rootScope, or in a service that holds members and other relevant data and functions. Then you can inject that service wherever you want, throughout the app.

like image 117
Mosho Avatar answered Feb 15 '23 20:02

Mosho