r3plica
r3plica

Reputation: 13397

angularjs service can't access property

I have a class set up like this:

.factory('DesignerService', function () {
    var kit = {
        clubName: 'Moss side archery club',
        teamName: 'The flying arrows',

        selectedColours: ['082140', '841c3d'],
        selectedGarments: ['hoody'],
        selectedDesign: "Angelus",

        total: '00.00',
        templates: []
    };

    var getTemplates = function () {
        var templates = [];

        console.log(kit);

        for (var i = 0; i < kit.selectedGarments.length; i++) {
            var garment = kit.selectedGarments[i];

            kit.templates.push('/assets/garments/' + garment + '.svg');
        }

        return kit;
    };

    var getColours = function () {
        return ['000000', 'FFFFFF', '00adef', 'ed008c', 'fef200', '2e3192', '00a652', 'ed1b24', 'c7c8ca', 'f14e23', '6c9d30', 'c0d731', 'f5a3c7', '816ab0', '082140', '1e4f2f', '5bcaf5', 'f04e3f', 'f68b1f', 'cdbe01', 'ee4d9b', '007193', '5f1e08', '841c3d'];
    };

    var getGarments = function () {
        return ['Shirt', 'Track top', 'Skirt', 'Hoody', 'Socks', 'Shorts', 'Track pants', 'Polo shirt', 'Beanie hat', 'T - shirt'];
    };

    var getDesigns = function () {
        return ['Angelus', 'Claudius', 'Equitius', 'Octavius', 'Valerius']
    }

    var addToArray = function (array, item) {
        array.push(item);
    };

    var removeFromArray = function (array, item) {
        var index = array.indexOf(item);

        if (index > -1) {
            array.splice(index, 1);
        }
    };

    var modifyArray = function (array, value) {
        var i = array.indexOf(value);

        if (i > -1) {
            removeFromArray(array, value);
        } else {
            addToArray(array, value);
        }
    }

    var setColour = function (colour) {
        return modifyArray(kit.selectedColours, colour);

        return kit;
    };

    var setGarment = function (garment) {
        console.log(kit);

        modifyArray(kit.selectedGarments, garment);

        var kit = getTemplates();

        console.log(kit);

        return kit;
    };

    var setDesign = function (design) {
        kit.selectedDesign = design;

        return kit;
    };

    return {
        kit: kit,

        getTemplates: getTemplates,
        getColours: getColours,
        getGarments: getGarments,
        getDesigns: getDesigns,

        setColour: setColour,
        setGarment: setGarment,
        setDesign: setDesign
    };
})

If I call the function setGarment the first console.log(kit) returns undefined so modifyArray errors at the kit.selectedGarments. But if I comment out the line:

//modifyArray(kit.selectedGarments, garment);

Then getTemplates actually logs the kit object and returns the updated object.

Can someone explain why?

Update 1

Ok, now I understand this hoisted stuff, I have redesigned my service to look like this:

.factory('DesignerService', function () {
    var self = this;

    var addToArray = function (array, item) {
        array.push(item);
    };

    var removeFromArray = function (array, item) {
        var index = array.indexOf(item);

        if (index > -1) {
            array.splice(index, 1);
        }
    };

    var modifyArray = function (array, value) {
        var i = array.indexOf(value);

        if (i > -1) {
            removeFromArray(array, value);
        } else {
            addToArray(array, value);
        }
    }

    self.kit = {
        clubName: 'Moss side archery club',
        teamName: 'The flying arrows',

        selectedColours: ['082140', '841c3d'],
        selectedGarments: ['hoody'],
        selectedDesign: "Angelus",

        total: '00.00',
        templates: []
    };

    self.getTemplates = function () {
        var templates = [];

        for (var i = 0; i < self.kit.selectedGarments.length; i++) {
            var garment = self.kit.selectedGarments[i];

            self.kit.templates.push('/assets/garments/' + garment + '.svg');
        }

        return self.kit;
    };

    self.getColours = function () {
        return ['000000', 'FFFFFF', '00adef', 'ed008c', 'fef200', '2e3192', '00a652', 'ed1b24', 'c7c8ca', 'f14e23', '6c9d30', 'c0d731', 'f5a3c7', '816ab0', '082140', '1e4f2f', '5bcaf5', 'f04e3f', 'f68b1f', 'cdbe01', 'ee4d9b', '007193', '5f1e08', '841c3d'];
    };

    self.getGarments = function () {
        return ['Shirt', 'Track top', 'Skirt', 'Hoody', 'Socks', 'Shorts', 'Track pants', 'Polo shirt', 'Beanie hat', 'T - shirt'];
    };

    self.getDesigns = function () {
        return ['Angelus', 'Claudius', 'Equitius', 'Octavius', 'Valerius']
    }

    self.setColour = function (colour) {
        return modifyArray(self.kit.selectedColours, colour);

        return self.kit;
    };

    self.setGarment = function (garment) {
        modifyArray(self.kit.selectedGarments, garment);

        self.getTemplates();

        return self.kit;
    };

    self.setDesign = function (design) {
        self.kit.selectedDesign = design;

        return self.kit;
    };

    return self;
})

Would this be the correct way to go about it?

Upvotes: 1

Views: 325

Answers (3)

Estus Flask
Estus Flask

Reputation: 223104

Since you're approaching Angular factory from OOP side and referring to public 'class' member, it is a good practice to refer to it as this.kit instead of kit (they won't be equal if service's kit property is changed). It also makes code unambiguous and readable.

The issue with local var kit is classic JS hoisting problem, as Bill Bergquist's answer thoroughly explains.

Upvotes: 1

dting
dting

Reputation: 39297

You are encountering a hoisting issue.

In javascript you can initialize and use variable before they are declared.

x = "hello";
y = "world";
var x, y;

document.getElementById("message").innerHTML = x + " " + y;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="message"></div>

The declaration is hoisted but the initialization is not.

var x = "hello";

document.getElementById("message").innerHTML = x + " " + y;

var y = "world";
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="message"></div>

Functions behave somewhat differently. A function declaration can be used in code before they are declared, That is because they are loaded before any code is run, hoisted.

alert(works());
function works() { ... };

Function expressions are different. They are not loaded until they are reached in the code.

alert(doesntWork());  
var doesntWork = function() { ... };

// Function declarations have their bodies hoisted.

document.getElementById("message1").innerHTML = y();

function x() { return "hello"; };
function y() { return x() + " world"; }

// Function expressions only load when the code is reached, they need to be declared before being used. 

// document.getElementById("message2").innerHTML = b(); <-- This would cause an error.

var a = function() { return "hello"; },
    b = function() { return a() + " world"; };

document.getElementById("message2").innerHTML = b();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="message1"></div>
<div id="message2"></div>

Upvotes: 1

Bill Bergquist
Bill Bergquist

Reputation: 263

This looks like a Javascript variable hoisting issue. The Javascript interpreter makes two passes through code. The first pass, it looks for variable declarations, and the second pass it executes the code. This is essentially doing the following to your code:

var kit = getTemplates()

becomes:

var kit;
kit = getTemplates();

At this point, the Javascript interpreter will make it's first pass, looking for variable declarations (var kit). The next pass will actually execute the code (kit = getTemplates()). Essentially, your code becomes:

var kit;

console.log(kit); // kit is undefined

modifyArray(kit.selectedGarments, garment); // kit is still undefined

kit = getTemplates(); // kit is no longer undefined

console.log(kit); // kit will log out properly here

return kit;

Hopefully this makes sense. The simple solution is to rename your scoped kit variable to something else (maybe scoped_kit). In general, it's best practice to not shadow variable names from one scope to another.

Hope this helps!

Upvotes: 4

Related Questions