# Function to ensure that at least one choice is selected

First here is my settings object below. insight, spike and momentum are binded to 3 checkboxes in the markup. The objective here is to prevent the user from deselecting all choices, 1 choice must remain selected.

var settings = {
user_id            : 0,
insight            : true,
spike              : true,
momentum           : true,
all_tickers        : true,
errorMsg           : false,
port_tickers       : [],
fav_tags           : [],
sensitivity        : 'medium',
};


So what I'm doing below is I create an Array with just the 3 keys from the settings object I want. Next I filter that array down to only the keys that are set to true. Next if the checkbox model is the same as the last item in the trueFilters Array I can then send back errorMsg = true.

function updateFilters(type) {
settings[type] = !settings[type];

// Create Array of filters that are true
var filterKeys = _.map(settings, function(filter, key) {
if (key === 'insight' || key === 'spike' || key === 'momentum') {
if (filter) {
return key;
} else {
return null;
}
} else {
return null;
}
});

// Remove any filters that are null/false
var trueFilters = _.remove(filterKeys, function(n) {
return n != null;
});

// If this filter is the last, prevent user from unselecting
if (trueFilters.length === 0) {
settings[type] = !settings[type];
settings.errorMsg = true;
}
}


if (key === 'insight' || key === 'spike' || key === 'momentum') {


Instead of looping through and checking these specific keys using an if, why not just create an array with the keys you're interested in and loop through that?

var filterKeys = ['insight', 'spike', 'momentum'];


Running on this, you can easily use map to gather the values, filter to reduce to only those that are true. Then checking if it's the last true filter will just be a matter of checking if what remains from a filter is an array with 1 as length.

Additionally, this function can be made so that it doesn't introduce side-effects or reduces the surface area of a side-effect. You can simply split off the logic to another function and return a boolean, while leaving the logic with side-effects (one that alters settings) in the caller.

function isLastFilter(settings) {
var filters = ['insight', 'spike', 'momentum'];

return filters.map(filter => settings[filter])
.filter(value => value === true)
.length === 1;
}

function updateFilters(type) {
var shouldUpdate  = !isLastFilter(settings);
settings[type]    = shouldUpdate ? settings[type] : !settings[type];
settings.errorMsg = !shouldUpdate;
}


On other things, settings.errorMsg shouldn't be part of the data. It's a computation of other settings. In this case, it's derived from a state where there's only one filter present. The problem here is that updateFilters is essentially a "sync" function, syncing settings.errorMsg to the state of the other settings. Lose this, and you'll get an invalid state.

Drop settings.errorMsg. If you're using an ng-if or something to toggle the error in the UI, it should be something like ng-if="isLastFilter(settings)".