I was looking to see if anyone could help me come up with a cleaner/DRY methods in duplicate of doing something like this?I would like to get the features that repeats in the 2 controllers to generalize my code.var propertiesModule = angular.module('app.properties', []);propertiesModule.controller('PropertiesCtrl', ['$scope', '$routeParams', 'PropertiesService', 'ApplicationService', 'PlatformService', 'Page', function ($scope, $routeParams, PropertiesService, ApplicationService, PlatformService, Page) {    Page.setTitle(Properties);    $scope.platform = $routeParams.platform;    $scope.platforms = [];    $scope.on_edit_platform = function (platform_name) {        /* Reset unit choice */        $scope.unit = undefined;    };    $scope.add_platform = function (platform_name) {        if (!_.contains($scope.platforms, platform_name)) {            $scope.platforms.push(platform_name);        }    };    $scope.delete_platform = function (platform) {        //Might be a bit tricky    };    $scope.on_edit_unit = function (unit) {        PropertiesService.getPropertiesMergedWithModel(properties# + $scope.application.name + # + $scope.application.version + # + $scope.platform + # + unit.name, unit.modelNamespaces).then(function (properties) {            $scope.properties = properties;        });    };    $scope.save_properties = function (properties) {        PropertiesService.save(properties).then(function (properties) {            PropertiesService.getModel($scope.unit.modelNamespaces).then(function (model) {                $scope.properties = properties.mergeWithModel(model);            });        });    };    $scope.on_concat_plateformes = function () {        var a = $scope.platforms.concat($scope.platformsCible);        for (var i = 0; i < a.length; ++i) {            for (var j = i + 1; j < a.length; ++j) {                if (a[i] === a[j])                    a.splice(j--, 1);            }        }        return a;    };    /* Get the application */    ApplicationService.get($routeParams.application, $routeParams.version).then(function (application) {        $scope.application = application;        /* If unit was mentionned in the route, try to find it */        /* If it does not exist show error */        if ($routeParams.unit) {            var actual_unit = _.find(application.units, function (unit) {                return unit.name === $routeParams.unit;            });            if (_.isUndefined(actual_unit)) {                $.notify(La brique technique mentionee dans l'url n'existe pas, error);            } else {                $scope.unit = actual_unit;                $scope.on_edit_unit(actual_unit);            }        }        ;    }, function (error) {        $.notify(error.data, error);    });    /* Find all the platforms */    PlatformService.get($routeParams.application, $routeParams.version).then(function (platforms) {        $scope.platforms = platforms;    }).then(function () {        /* If platform was mentionned in the route, try to find it or add it */        if ($scope.platform) $scope.add_platform($scope.platform);    });}]);propertiesModule.controller('PropertiesCompareCtrl', ['$scope', '$routeParams', 'PropertiesService', 'ApplicationService', 'PlatformService', 'Page', function ($scope, $routeParams, PropertiesService, ApplicationService, PlatformService, Page) {    Page.setTitle(Comparer Properties par versions);    $scope.platform = $routeParams.platform;    $scope.platforms = [];    $scope.platformsIdentique = [];    var propertiesSources;    $scope.on_edit_unit = function (unit) {        PropertiesService.getPropertiesMergedWithModel(properties# + $scope.application.name + # + $scope.application.version + # + $scope.platform + # + unit.name, unit.modelNamespaces).then(function (properties) {            $scope.properties = properties;            unit.modelNamespaces = [app# + $scope.applicationCible.name + # + $scope.applicationCible.version + # + unit.name + , technos# + $scope.platform + # + $scope.applicationCible.version];            PropertiesService.getPropertiesMergedWithModel(properties# + $scope.applicationCible.name + # + $scope.applicationCible.version + # + $scope.platform + # + unit.name, unit.modelNamespaces).then(function (propertiesCible) {                var tab1 = [];                for (var i = 1; i <= $scope.properties.key_value_properties.length - 1; i++) {                    tab1.push({                        key: $scope.properties.key_value_properties[i].name,                        value: $scope.properties.key_value_properties[i].value                    });                }                var tab2 = [];                for (var i = 1; i <= propertiesCible.key_value_properties.length - 1; i++) {                    tab2.push({                        key: propertiesCible.key_value_properties[i].name,                        value: propertiesCible.key_value_properties[i].value                    });                }                var tab1 = _.sortBy(tab1, 'key');                var tab2 = _.sortBy(tab2, 'key');                var obj1 = arrayToObject(tab1);                var obj2 = arrayToObject(tab2);                $scope.properties.differences = [];                $scope.properties.absentInTab1 = [];                $scope.properties.presentInTab1 = [];                for (var i in obj1) {                    if (typeof(obj2[i]) === 'undefined') {                        // presentInTab1.push({ key: i, oldValue: obj1[i], newValue: null });                        $scope.properties.presentInTab1.push({name: i, valueRef: obj1[i], valueCible: null});                    }                    else if (obj1[i] !== obj2[i]) {                        //differences.push({ key: i, oldValue: obj1[i], newValue: obj2[i] });                        $scope.properties.differences.push({name: i, valueRef: obj1[i], valueCible: obj2[i]});                    }                }                for (var i in obj2) {                    if (typeof(obj1[i]) === 'undefined') {                        // absentInTab1.push({ key: i, oldValue: null, newValue: obj2[i] });                        $scope.properties.absentInTab1.push({name: i, valueRef: null, valueCible: obj2[i]});                    }                }                $scope.properties.groups = [                    { name: 'Diff Version ' + $scope.application.version + ' / Version ' + $scope.applicationCible.version,                        members: $scope.properties.differences                    },                    { name: 'Proprits en + sur Version ' + $scope.application.version + ' / Version ' + $scope.applicationCible.version,                        members: $scope.properties.presentInTab1                    },                    { name: 'Proprits - sur Version ' + $scope.application.version + ' / Version ' + $scope.applicationCible.version,                        members: $scope.properties.absentInTab1                    }                ];            }, function (error) {            });        });    };    function arrayToObject(array) {        var object = {};        for (var i = 0; i < array.length; i++) {            object[array[i].key] = array[i].value;        }        return object;    }    function dynamicSort(property) {        var sortOrder = 1;        if (property[0] === -) {            sortOrder = -1;            property = property.substr(1);        }        return function (a, b) {            var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;            return result * sortOrder;        }    }    ApplicationService.get($routeParams.application, $routeParams.version).then(function (application) {        $scope.application = application;        /* If unit was mentionned in the route, try to find it */        /* If it does not exist show error */        if ($routeParams.unit) {            var actual_unit = _.find(application.units, function (unit) {                return unit.name === $routeParams.unit;            });            if (_.isUndefined(actual_unit)) {                $.notify(La brique technique mentionee dans l'url n'existe pas, error);            } else {                $scope.unit = actual_unit;                $scope.on_edit_unit(actual_unit);            }        }        ;    }, function (error) {        $.notify(error.data, error);    });    /*   Get the application cible for compare*/    ApplicationService.get($routeParams.application, $routeParams.versionCible).then(function (applicationCible) {        $scope.applicationCible = applicationCible;        /* If unit was mentionned in the route, try to find it*/        /* If it does not exist show error*/        if ($routeParams.unit) {            var actual_unit = _.find(applicationCible.units, function (unit) {                return unit.name === $routeParams.unit;            });            if (_.isUndefined(actual_unit)) {                $.notify(La brique technique mentionee dans l'url n'existe pas, error);            } else {                $scope.unit = actual_unit;                $scope.on_edit_unit(actual_unit);            }        }        ;    }, function (error) {        $.notify(error.data, error);    });    /* Find all the platforms */    PlatformService.get($routeParams.application, $routeParams.version).then(function (platforms) {        $scope.platforms = platforms;    }).then(function () {        /* If platform was mentionned in the route, try to find it or add it */        if ($scope.platform) $scope.add_platform($scope.platform);    });    /* Find all the platforms cible */    if ($routeParams.versionCible != null) {        PlatformService.get($routeParams.application, $routeParams.versionCible).then(function (platforms) {            var a = $scope.platforms;            var b = platforms;            a = a.sort()            $scope.platformsIdentique = _.intersection(a, b)        });    }}]);propertiesModule.controller('PropertiesComparePlateformesCtrl', ['$scope', '$routeParams', 'PropertiesService', 'ApplicationService', 'PlatformService', 'Page', function ($scope, $routeParams, PropertiesService, ApplicationService, PlatformService, Page) {    Page.setTitle(Comparer Application Plateformes);    $scope.platform = $routeParams.platform;    $scope.platforms = [];    var propertiesSources;    $scope.enableCompare = function () {        return !($scope.mySelectRef && $scope.mySelectCible);    };    /*   Get the application cible for compare*/    ApplicationService.get($routeParams.application, $routeParams.version).then(function (applicationPlateformes) {        $scope.applicationPlateformes = applicationPlateformes;        /* If unit was mentionned in the route, try to find it*/        /* If it does not exist show error*/        if ($routeParams.unit) {            var actual_unit = _.find(applicationPlateformes.units, function (unit) {                return unit.name === $routeParams.unit;            });            if (_.isUndefined(actual_unit)) {                $.notify(La brique technique mentionee dans l'url n'existe pas, error);            } else {                $scope.unit = actual_unit;                $scope.on_edit_unit(actual_unit);            }        }        ;    }, function (error) {        $.notify(error.data, error);    });    /* Find all the platforms */    PlatformService.get($routeParams.application, $routeParams.version).then(function (platforms) {        $scope.platforms = platforms;    }).then(function () {        /* If platform was mentionned in the route, try to find it or add it */        if ($scope.platform) $scope.add_platform($scope.platform);    });    $scope.on_compare_plateformes = function (unit) {        PropertiesService.getPropertiesMergedWithModel(properties# + $scope.applicationPlateformes.name + # + $scope.applicationPlateformes.version + # + $scope.mySelectRef + # + unit.name, unit.modelNamespaces).then(function (propertiesRef) {            $scope.properties = propertiesRef;            PropertiesService.getPropertiesMergedWithModel(properties# + $scope.applicationPlateformes.name + # + $scope.applicationPlateformes.version + # + $scope.mySelectCible + # + unit.name, unit.modelNamespaces).then(function (propertiesCible) {                var tab1 = [];                for (var i = 1; i <= $scope.properties.key_value_properties.length - 1; i++) {                    tab1.push({                        key: $scope.properties.key_value_properties[i].name,                        value: $scope.properties.key_value_properties[i].value                    });                }                var tab2 = [];                for (var i = 1; i <= propertiesCible.key_value_properties.length - 1; i++) {                    tab2.push({                        key: propertiesCible.key_value_properties[i].name,                        value: propertiesCible.key_value_properties[i].value                    });                }                var tab1 = _.sortBy(tab1, 'key');                var tab2 = _.sortBy(tab2, 'key');                var obj1 = arrayToObject(tab1);                var obj2 = arrayToObject(tab2);                $scope.properties.differences = [];                $scope.properties.absentInTab1 = [];                $scope.properties.presentInTab1 = [];                for (var i in obj1) {                    if (typeof(obj2[i]) === 'undefined') {                        // presentInTab1.push({ key: i, oldValue: obj1[i], newValue: null });                        $scope.properties.presentInTab1.push({name: i, valueRef: obj1[i], valueCible: null});                    }                    else if (obj1[i] !== obj2[i]) {                        //differences.push({ key: i, oldValue: obj1[i], newValue: obj2[i] });                        $scope.properties.differences.push({name: i, valueRef: obj1[i], valueCible: obj2[i]});                    }                }                for (var i in obj2) {                    if (typeof(obj1[i]) === 'undefined') {                        // absentInTab1.push({ key: i, oldValue: null, newValue: obj2[i] });                        $scope.properties.absentInTab1.push({name: i, valueRef: null, valueCible: obj2[i]});                    }                }                $scope.properties.groups = [                    { name: 'Diff Plateforme ' + $scope.MySelectRef + ' / Plateforme ' + $scope.MySelectCible,                        members: $scope.properties.differences                    },                    { name: 'Proprits en + sur Plateforme ' + $scope.MySelectRef + ' / Plateforme ' + $scope.MySelectCible,                        members: $scope.properties.presentInTab1                    },                    { name: 'Proprits - sur Plateforme ' + $scope.MySelectRef + ' / Plateforme ' + $scope.MySelectCible,                        members: $scope.properties.absentInTab1                    }                ];            });        });    };    function arrayToObject(array) {        var object = {};        for (var i = 0; i < array.length; i++) {            object[array[i].key] = array[i].value;        }        return object;    }    function dynamicSort(property) {        var sortOrder = 1;        if (property[0] === -) {            sortOrder = -1;            property = property.substr(1);        }        return function (a, b) {            var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;            return result * sortOrder;        }    }}]);propertiesModule.directive('propertiesList', function () {    return {        restrict: 'E',        scope: {            properties: '='        },        templateUrl: properties/properties-list.html,        link: function (scope, element, attrs) {        }    };});propertiesModule.directive('propertiesListCompare', function () {    return {        restrict: 'E',        scope: {            properties: '='        },        templateUrl: properties/properties-list-compare.html,        link: function (scope, element, attrs) {        }    };});propertiesModule.factory('Properties', function () {    var Properties = function (data) {        angular.extend(this, {            namespace: ,            key_value_properties: [],            iterable_properties: [],            versionID: -1        }, data);        this.hasKey = function (name) {            return _.some(this.key_value_properties, function (key) {                return key.name === name;            });        };        this.hasIterable = function (name) {            return _.some(this.iterable_properties, function (key) {                return key.name === name;            });        };        this.mergeWithModel = function (model) {            var me = this;            /* Mark key_values that are in the model */            _.each(this.key_value_properties, function (key_value) {                key_value.inModel = model.hasKey(key_value.name);            });            _.each(this.iterable_properties, function (iterable) {                iterable.inModel = model.hasIterable(iterable.name);            });            /* Add key_values that are only in the model */            _(model.key_value_properties).filter(function (model_key_value) {                return !me.hasKey(model_key_value.name);            }).each(function (model_key_value) {                me.key_value_properties.push({                    name: model_key_value.name,                    comment: model_key_value.comment,                    value: ,                    inModel: true                });            });            _(model.iterable_properties).filter(function (model_iterable) {                return !me.hasIterable(model_iterable.name);            }).each(function (model_iterable) {                me.iterable_properties.push({                    name: model_iterable.name,                    comment: model_iterable.comment,                    fields: model_iterable.fields,                    inModel: true                });            });            return this;        };        this.toAppEntity = function () {            return {                namespace: this.namespace,                versionID: this.versionID,                key_value_properties: _.map(this.key_value_properties, function (kvp) {                    return {                        name: kvp.name,                        comment: kvp.comment,                        value: kvp.value                    }                }),                iterable_properties: _.map(this.iterable_properties, function (ip) {                    return {                        name: ip.name,                        comment: ip.comment,                        fields: ip.fields                    }                })            }        }    };    return Properties;});propertiesModule.factory('PropertiesService', ['$http', 'Properties', function ($http, Properties) {    return {        getModel: function (namespaces) {            var namespaces_as_string = _.isArray(namespaces) ? namespaces.join(,) : namespaces;            return $http.get('rest/properties/model/' + encodeURIComponent(namespaces_as_string)).then(function (response) {                return new Properties(response.data);            }, function (error) {                return new Properties({});            });        },        get: function (namespace) {            return $http.get('rest/properties/' + encodeURIComponent(namespace)).then(function (response) {                return new Properties(response.data);            }, function (error) {                $.notify(error.data, error);                throw error;            });        },        getPropertiesMergedWithModel: function (properties_namespace, model_namespaces) {            var me = this;            return this.getModel(model_namespaces).then(function (model) {                return me.get(properties_namespace).then(function (properties) {                    return properties.mergeWithModel(model);                }, function (error) {                    var properties = new Properties({namespace: properties_namespace});                    return properties.mergeWithModel(model);                });            });        },        save: function (properties) {            properties = properties.toAppEntity();            if (properties.versionID < 0) {                return $http.post('rest/properties/' + encodeURIComponent(properties.namespace), properties).then(function (response) {                    $.notify(Les proprietes ont bien ete crees, success);                    return new Properties(response.data);                }, function (error) {                    $.notify(error.data, error);                });            } else {                return $http.put('rest/properties/' + encodeURIComponent(properties.namespace), properties).then(function (response) {                    $.notify(Les proprietes ont bien ete mises a jour, success);                    return new Properties(response.data)                }, function (error) {                    $.notify(error.data, error);                });            }        }    }}]);
Property management
javascript;angular.js
A short, nitpicking review:Here, there seems to be no good reason to choose key and value instead of name and value, it just seems like it makes things more complicated.        var tab1 = [];        for (var i = 1; i <= $scope.properties.key_value_properties.length - 1; i++) {            tab1.push({                key: $scope.properties.key_value_properties[i].name,                value: $scope.properties.key_value_properties[i].value            });        }        var tab2 = [];        for (var i = 1; i <= propertiesCible.key_value_properties.length - 1; i++) {            tab2.push({                key: propertiesCible.key_value_properties[i].name,                value: propertiesCible.key_value_properties[i].value            });        }could be        var tab1 = $scope.properties.key_value_properties,            tab2 = propertiesCible.key_value_properties;Also, mandatory bash on using French in variables names. Don't do it, propertiesCible -> propertiesTargetConsider using syntax sugar (in this example using shortcut variables) and remove useless comments, this        $scope.properties.differences = [];        $scope.properties.absentInTab1 = [];        $scope.properties.presentInTab1 = [];        for (var i in obj1) {            if (typeof(obj2[i]) === 'undefined') {                // presentInTab1.push({ key: i, oldValue: obj1[i], newValue: null });                $scope.properties.presentInTab1.push({name: i, valueRef: obj1[i], valueCible: null});            }            else if (obj1[i] !== obj2[i]) {                //differences.push({ key: i, oldValue: obj1[i], newValue: obj2[i] });                $scope.properties.differences.push({name: i, valueRef: obj1[i], valueCible: obj2[i]});            }        }        for (var i in obj2) {            if (typeof(obj1[i]) === 'undefined') {                // absentInTab1.push({ key: i, oldValue: null, newValue: obj2[i] });                $scope.properties.absentInTab1.push({name: i, valueRef: null, valueCible: obj2[i]});            }        }could be         var differences = $scope.properties.differences = [],            absentInTab1 = $scope.properties.absentInTab1 = [],            presentInTab1 = $scope.properties.presentInTab1 = [];        for (var i in obj1) {            if (typeof(obj2[i]) === 'undefined') {                presentInTab1.push({name: i, valueRef: obj1[i], valueCible: null});            }            else if (obj1[i] !== obj2[i]) {                differences.push({name: i, valueRef: obj1[i], valueCible: obj2[i]});            }        }        for (var i in obj2) {            if (typeof(obj1[i]) === 'undefined') {                absentInTab1.push({name: i, valueRef: null, valueCible: obj2[i]});            }        }I strive to never use null but undefined insteadBe more ardent in reducing copy paste lines, the following seems a good example:save: function (properties) {    properties = properties.toAppEntity();    if (properties.versionID < 0) {        return $http.post('rest/properties/' + encodeURIComponent(properties.namespace), properties).then(function (response) {            $.notify(Les proprietes ont bien ete crees, success);            return new Properties(response.data);        }, function (error) {            $.notify(error.data, error);        });    } else {        return $http.put('rest/properties/' + encodeURIComponent(properties.namespace), properties).then(function (response) {            $.notify(Les proprietes ont bien ete mises a jour, success);            return new Properties(response.data)        }, function (error) {            $.notify(error.data, error);        });    }}you could try this:save: function (properties) {    var createdMessage = 'Les proprietes ont bien ete crees',        updatedMessage = 'Les proprietes ont bien ete mises a jour',        message = properties.versionID < 0 ? createdMessage : updatedMessage;    properties = properties.toAppEntity();        return $http.put('rest/properties/' + encodeURIComponent(properties.namespace), properties).then(function (response) {            $.notify( message  , success);            return new Properties(response.data)        }, function (error) {            $.notify(error.data, error);        });}You could reduce the vertical stretching in your code quite a bit by using _.pick():            return {                name: kvp.name,                comment: kvp.comment,                value: kvp.value            }could be            return _.pick( kvp, 'name', 'comment', 'value');