0

I need a Factory object that can be applied with a $scope.property to get a result. I also need to know if either the modifier in the factory instance, or the $scope.property changes to update the result. How I am seeing this pattern might be wrong of course.

app.factory('MyFactory',
    function () {

        var MyFactory = function (name, modifier) {
            this.name = name;
            this.result = 0;
            this.modifier = modifier;
        }

        //I might want to call this when modifier or MyProperty changes
        MyFactory.prototype.modifyingMethod = function () {
            this.result = this.modifier * //externalProperty;
        }

        MyFactory.prototype.watcher = function () {
            //no idea what I will do here or if I need this at all
            // I need to recalculate the result like with $watch
            this.modifyingMethod();
        }

        return MyFactory;
    }
)

app.controller('MyCtrl'
    function($scope, MyFactory) {

        $scope.MyProperty = 42;

        $scope.MyFactoryOptions = [
            new MyFactory('Option1', 1),
            new MyFactory('Option2', 2),
            new MyFactory('Option3', 3),
            new MyFactory('Option4', 4),
            new MyFactory('Option5', 5)
        ];
    }

So I have the problem that I need to $watch MyProperty and the modifier (it can be changed bu users) so I can change the result. If the Property is a value type passing it into the Factory constructor will not work. Perhaps I could pass a function in the returns MyProperty.

Can I set up internal $watch on the factory. If I would do this outside of the factory, in the controller, I would need to do it for each instance. Should I perhaps set up some sort of register method on my factory object?

Are there any suggestions, patterns or something I might want to use?

isherwood
  • 58,414
  • 16
  • 114
  • 157
Ingó Vals
  • 4,788
  • 14
  • 65
  • 113
  • A factory is a singleton in Angular and should be used as one. why do you want to create `new MyFactory`? Use getters and setters instead and get/set them, when watching the changed parameters. – Rias Mar 11 '15 at 16:17
  • @Rias Are you certain? I was pretty sure that Service is a singleton while a factory churned out usable objects. My name is pretty misleading of course, It could be ´Option´, and the factory would actually be a ´OptionFactory´ (but sadly we can't name the factory differently). – Ingó Vals Mar 11 '15 at 16:27
  • As long as your factory does not return a primitive value it's a singleton as well. A factory is a kind of service in Angular, but actually a service as well. – Rias Mar 11 '15 at 16:32
  • @Rias I'm confused, I understand using the getters and setters to update my properties, and that makes sense. But I don't get what I'm doing wrong with the options. How would you create multiple options like this where each one would have the same logic to it, define it on the scope? Create a controller for each one? – Ingó Vals Mar 11 '15 at 16:40

1 Answers1

1

Basically you could understand your Factory as an interface to a collection of objects (either an array or associative array respectively pure javascript object).

I find your approach with Objects very appealing and I am sure you can achieve similar things. Still I put together a fiddle, that shows how I would solve the problem:

In a MVC pattern your Factory would be the model, the controller should be as simple as possible and your HTML with directives represents the view.

You controller watches for changes from the user ($scope.MyProperty with $watch). While the model is self aware of any depending external property changes. Note that the changes of the ExternalObject service/factory will only be recognizable, if those aren't primitive values. That is why in the ExternalObject factory I return the whole object.

In a perfect world you wouldn't need to listen for changes with an interval, but will receive a javascript event. If this is possible, do it! Note that object updates out of Angular's scope will need you to do a $scope.$apply(), if you want to see the changes in the view. $intervaland $timeout both call a $scope.apply(), so using them is best practice.

Actually there still has to be a lot of cleanup to be done in this code, but it might give you a basic idea how to use an alternative structure:

var app = angular.module('yourApp', []);
window.yourGlobalObject = {};

setInterval(function () {
    yourGlobalObject.externalProperty = Math.floor(Math.random() * 5000);
}, 1000);

app.factory('ExternalObject', ['$window', function ($window) {
    return $window.yourGlobalObject;
}]);

app.factory('MyFactory', ['$interval', 'ExternalObject', function ($interval, ExternalObject) {
    var options = [{
        name: 'Option1',
        modifier: 1
    }, {
        name: 'Option2',
        modifier: 2
    }, {
        name: 'Option3',
        modifier: 3
    }],
        cachedExternalProperty = 0,
    cachedMyProperty = 0;

    MyFactory = {
        getOptions: function () {
            return options;
        },
        addOption: function (name, modifier) {
            options.push({
                name: name,
                modifier: modifier
            });
        },
        setMyProperty: function (value) {
            cachedMyProperty = value;
        },
        setResults: function (myProperty) {
            angular.forEach(options, function (option, key) {
                option.result = option.modifier * ExternalObject.externalProperty * myProperty;
            });
            console.log(options);
        }
    };

    // let the service check for updates in the external property, if changed
    $interval(function () {
        if (cachedExternalProperty !== ExternalObject.externalProperty) {
            cachedExternalProperty = ExternalObject.externalProperty;
            MyFactory.setResults(cachedMyProperty);
        }
    }, 1000);

    return MyFactory;
}]);

app.controller('MyCtrl', ['$scope', 'MyFactory', function ($scope, MyFactory) {

    $scope.MyProperty = 42;
    $scope.MyFactoryOptions = MyFactory.getOptions();
    $scope.setResults = function () {
        MyFactory.setResults($scope.MyProperty);
    };
    
    $scope.$watch('MyProperty', function (value) {
        MyFactory.setMyProperty(value)
        MyFactory.setResults(value);
    });

}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
    <section ng-app="yourApp" ng-controller="MyCtrl">
        <button ng-click="setResults(MyProperty)">Update Results</button>
        <div ng-repeat="factory in MyFactoryOptions">{{factory.name}} {{factory.result}}</div>
        
        <input type="number" ng-model="MyProperty">
    </section>
</body>
Rias
  • 1,956
  • 22
  • 33