2

Consider the following example:

angular.module('demo')
    .service('MyService', function () {
        this.fn = function () {
            console.log('MyService:fn');
        };
    })
    .factory('MyFactory', function () {
        function fn() {
            console.log('MyFactory:fn');
        }
        return { fn: fn };
    })
    .value('MyValue', {
        fn: function () {
            console.log('MyValue:fn');
        }
    })
    .constant('MyConstant', {
        fn: function () {
            console.log('MyConstant:fn');
        }
    })
    .run(function (MyService, MyFactory, MyValue, MyConstant) {
        MyService.fn();
        MyFactory.fn();
        MyValue.fn();
        MyConstant.fn();

        MyService.fn = undefined;
        MyFactory.fn = undefined;
        MyValue.fn = undefined;
        MyConstant.fn = undefined;
    })
    .run(function (MyService, MyFactory, MyValue, MyConstant) {
        MyService.fn();
        MyFactory.fn();
        MyValue.fn();
        MyConstant.fn();
    });

When the first run() is executed, all 4 console logs will be executed and print something on the console. Then I set each of the providers fn function to undefined for simplification purposes, say someone rewrote this function somewhere (which is something I want to prevent).

On the second run() block, everything is undefined and errors will be thrown. I'm confused by this... Shouldn't at least some of them (constant is the first to come to mind) be immutable objects?

Is this the expected behavior or am I doing something wrong?

rfgamaral
  • 16,546
  • 57
  • 163
  • 275

2 Answers2

1

Why is this a surprise. Angular is a framework running on top of Javascript, and Javascript is a dynamic language. Your question is really about the language construct.

First, recognize that all the calls are, at the end of the day, registering a provider that would return an object to be injected. .service, .factory, and .value are just short hands for .provider (.constant is a bit different).

Having established that there is no difference between them once the object is injected, all you then need to concern yourself with is how to make that object immutable.

Javascript provides Object.freeze function, so for example, you could do:

var immutable = {
        fn: function () {
            console.log('MyConstant:fn');
        }
    };
Object.freeze(immutable);

app.constant("MyConstant", immutable);
New Dev
  • 48,427
  • 12
  • 87
  • 129
1

The functions constant, factory, service etc. allow you to create Javascript objects that are created once, then cached by angular, and injected into components as/when needed. Because these are Javascript objects, then (ignoring the possibility of using freeze) any bit of code has access to these objects can modify properties on them, as you have demonstrated.

Although properties on the objects themselves can be modified, the object itself can't be changed to another one, so if you really want an immutable provider, that is safe from all tampering, one way that I can think of is to use a factory that returns not an object, but a getter function:

app.factory('MyFactory', function() {
  var functions = {
    myFunctionName: function() {
    }
  };

  return function(functionName) {
    return functions[functionName];
  };
});

which can be used, say in a controller, as

app.controller('MyController', function(MyFactory) {
  MyFactory('myFunctionName')();
});

A drawback of using this over the more traditional way of exposing all the methods and allowing the possibility of the object to be modified is that I suspect unit testing could be more complicated: you wouldn't be able to easily create spies on your factory methods by using createSpy(MyFactory, 'myFunctionName').

Michal Charemza
  • 25,940
  • 14
  • 98
  • 165