Is [] parameter in the AngularJS module definition compulsory? - angularjs

In definition of AngularJS module, [] is a parameter for other depended module on this module.
<script>
var app = angular.module('myApp',[]);
app.controller("myCtrl", function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
});
</script>
My Question is,
Is this parameter [] necessary, because the the following link or example they didn't mentioned [] parameter, but in above example(w3schooles), if we remove '[]' parameter then code will not give correct output see it?
Please see the this link openstack, they are not using [] parameter
var module = angular.module('hz.dashboard.launch-instance');
/**
* #ngdoc service
* #name launchInstanceModel
*
* #description
* This is the M part in MVC design pattern for launch instance
* wizard workflow. It is responsible for providing data to the
* view of each step in launch instance workflow and collecting
* user's input from view for creation of new instance. It is
* also the center point of communication between launch instance
* UI and services API.
*/
module.factory('launchInstanceModel', ['$q',
'cinderAPI',
'glanceAPI',
'keystoneAPI',
'neutronAPI',
'novaAPI',
'novaExtensions',
'securityGroup',
'serviceCatalog',
function ($q,
cinderAPI,
glanceAPI,
keystoneAPI,
neutronAPI,
novaAPI,
novaExtensions,
securityGroup,
serviceCatalog) {
var initPromise,
allNamespacesPromise;
// Constants (const in ES6)
var NON_BOOTABLE_IMAGE_TYPES = ['aki', 'ari'],
SOURCE_TYPE_IMAGE = 'image',
SOURCE_TYPE_SNAPSHOT = 'snapshot',
SOURCE_TYPE_VOLUME = 'volume',
SOURCE_TYPE_VOLUME_SNAPSHOT = 'volume_snapshot';
/**
* #ngdoc model api object
*/
var model = {
initializing: false,
initialized: false,
/**
* #name newInstanceSpec
*
* #description
* A dictionary like object containing specification collected from user's
* input. Its required properties include:
*
* #property {String} name: The new server name.
* #property {String} source_type: The type of source
* Valid options: (image | snapshot | volume | volume_snapshot)
* #property {String} source_id: The ID of the image / volume to use.
* #property {String} flavor_id: The ID of the flavor to use.
*
* Other parameters are accepted as per the underlying novaclient:
* - https://github.com/openstack/python-novaclient/blob/master/novaclient/v2/servers.py#L417
* But may be required additional values as per nova:
* - https://github.com/openstack/horizon/blob/master/openstack_dashboard/api/rest/nova.py#L127
*
* The JS code only needs to set the values below as they are made.
* The createInstance function will map them appropriately.
*/
// see initializeNewInstanceSpec
newInstanceSpec: {},
/**
* cloud service properties, they should be READ-ONLY to all UI controllers
*/
availabilityZones: [],
flavors: [],
allowedBootSources: [],
images: [],
allowCreateVolumeFromImage: false,
arePortProfilesSupported: false,
imageSnapshots: [],
keypairs: [],
metadataDefs: {
flavor: null,
image: null,
volume: null
},
networks: [],
neutronEnabled: false,
novaLimits: {},
profiles: [],
securityGroups: [],
volumeBootable: false,
volumes: [],
volumeSnapshots: [],
/**
* api methods for UI controllers
*/
initialize: initialize,
createInstance: createInstance
};
// Local function.
function initializeNewInstanceSpec(){
model.newInstanceSpec = {
availability_zone: null,
admin_pass: null,
config_drive: false,
user_data: '', // REQUIRED Server Key. Null allowed.
disk_config: 'AUTO',
flavor: null, // REQUIRED
instance_count: 1,
key_pair: [], // REQUIRED Server Key
name: null, // REQUIRED
networks: [],
profile: {},
security_groups: [], // REQUIRED Server Key. May be empty.
source_type: null, // REQUIRED for JS logic (image | snapshot | volume | volume_snapshot)
source: [],
vol_create: false, // REQUIRED for JS logic
vol_device_name: 'vda', // May be null
vol_delete_on_terminate: false,
vol_size: 1
};
}
/**
* #ngdoc method
* #name launchInstanceModel.initialize
* #returns {promise}
*
* #description
* Send request to get all data to initialize the model.
*/
function initialize(deep) {
var deferred, promise;
// Each time opening launch instance wizard, we need to do this, or
// we can call the whole methods `reset` instead of `initialize`.
initializeNewInstanceSpec();
if (model.initializing) {
promise = initPromise;
} else if (model.initialized && !deep) {
deferred = $q.defer();
promise = deferred.promise;
deferred.resolve();
} else {
model.initializing = true;
model.allowedBootSources.length = 0;
promise = $q.all([
getImages(),
novaAPI.getAvailabilityZones().then(onGetAvailabilityZones, noop),
novaAPI.getFlavors(true, true).then(onGetFlavors, noop),
novaAPI.getKeypairs().then(onGetKeypairs, noop),
novaAPI.getLimits().then(onGetNovaLimits, noop),
securityGroup.query().then(onGetSecurityGroups, noop),
serviceCatalog.ifTypeEnabled('network').then(getNetworks, noop),
serviceCatalog.ifTypeEnabled('volume').then(getVolumes, noop)
]);
promise.then(
function() {
model.initializing = false;
model.initialized = true;
// This provides supplemental data non-critical to launching
// an instance. Therefore we load it only if the critical data
// all loads successfully.
getMetadataDefinitions();
},
function () {
model.initializing = false;
model.initialized = false;
}
);
}
return promise;
}
/**
* #ngdoc method
* #name launchInstanceModel.createInstance
* #returns {promise}
*
* #description
* Send request for creating server.
*/
function createInstance() {
var finalSpec = angular.copy(model.newInstanceSpec);
cleanNullProperties();
setFinalSpecBootsource(finalSpec);
setFinalSpecFlavor(finalSpec);
setFinalSpecNetworks(finalSpec);
setFinalSpecKeyPairs(finalSpec);
setFinalSpecSecurityGroups(finalSpec);
return novaAPI.createServer(finalSpec);
}
function cleanNullProperties(finalSpec){
// Initially clean fields that don't have any value.
for (var key in finalSpec) {
if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null) {
delete finalSpec[key];
}
}
}
//
// Local
//
function onGetAvailabilityZones(data) {
model.availabilityZones.length = 0;
push.apply(model.availabilityZones, data.data.items
.filter(function (zone) {
return zone.zoneState && zone.zoneState.available;
})
.map(function (zone) {
return zone.zoneName;
})
);
if(model.availabilityZones.length > 0) {
model.newInstanceSpec.availability_zone = model.availabilityZones[0];
}
}
// Flavors
function onGetFlavors(data) {
model.flavors.length = 0;
push.apply(model.flavors, data.data.items);
}
function setFinalSpecFlavor(finalSpec) {
if ( finalSpec.flavor ) {
finalSpec.flavor_id = finalSpec.flavor.id;
} else {
delete finalSpec.flavor_id;
}
delete finalSpec.flavor;
}
// Keypairs
function onGetKeypairs(data) {
angular.extend(
model.keypairs,
data.data.items.map(function (e) {
e.keypair.id = e.keypair.name;
return e.keypair;
}));
}
function setFinalSpecKeyPairs(finalSpec) {
// Nova only wants the key name. It is a required field, even if None.
if(!finalSpec.key_name && finalSpec.key_pair.length === 1){
finalSpec.key_name = finalSpec.key_pair[0].name;
} else if (!finalSpec.key_name) {
finalSpec.key_name = null;
}
delete finalSpec.key_pair;
}
// Security Groups
function onGetSecurityGroups(data) {
model.securityGroups.length = 0;
push.apply(model.securityGroups, data.data.items);
// set initial default
if (model.newInstanceSpec.security_groups.length === 0 &&
model.securityGroups.length > 0) {
model.securityGroups.forEach(function (securityGroup) {
if (securityGroup.name === 'default') {
model.newInstanceSpec.security_groups.push(securityGroup);
}
});
}
}
function setFinalSpecSecurityGroups(finalSpec) {
// pull out the ids from the security groups objects
var security_group_ids = [];
finalSpec.security_groups.forEach(function(securityGroup){
if(model.neutronEnabled) {
security_group_ids.push(securityGroup.id);
} else {
security_group_ids.push(securityGroup.name);
}
});
finalSpec.security_groups = security_group_ids;
}
// Networks
function getNetworks() {
return neutronAPI.getNetworks().then(onGetNetworks, noop);
}
function onGetNetworks(data) {
model.neutronEnabled = true;
model.networks.length = 0;
push.apply(model.networks, data.data.items);
}
function setFinalSpecNetworks(finalSpec) {
finalSpec.nics = [];
finalSpec.networks.forEach(function (network) {
finalSpec.nics.push(
{
"net-id": network.id,
"v4-fixed-ip": ""
});
});
delete finalSpec.networks;
}
// Boot Source
function getImages(){
return glanceAPI.getImages({status:'active'}).then(onGetImages);
}
function isBootableImageType(image){
// This is a blacklist of images that can not be booted.
// If the image container type is in the blacklist
// The evaluation will result in a 0 or greater index.
return NON_BOOTABLE_IMAGE_TYPES.indexOf(image.container_format) < 0;
}
function onGetImages(data) {
model.images.length = 0;
push.apply(model.images, data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(!image.properties || image.properties.image_type !== 'snapshot');
}));
addAllowedBootSource(model.images, SOURCE_TYPE_IMAGE, gettext('Image'));
model.imageSnapshots.length = 0;
push.apply(model.imageSnapshots,data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(image.properties && image.properties.image_type === 'snapshot');
}));
addAllowedBootSource(model.imageSnapshots, SOURCE_TYPE_SNAPSHOT, gettext('Instance Snapshot'));
}
function getVolumes(){
var volumePromises = [];
// Need to check if Volume service is enabled before getting volumes
model.volumeBootable = true;
addAllowedBootSource(model.volumes, SOURCE_TYPE_VOLUME, gettext('Volume'));
addAllowedBootSource(model.volumeSnapshots, SOURCE_TYPE_VOLUME_SNAPSHOT, gettext('Volume Snapshot'));
volumePromises.push(cinderAPI.getVolumes({ status: 'available', bootable: 1 }).then(onGetVolumes));
volumePromises.push(cinderAPI.getVolumeSnapshots({ status: 'available' }).then(onGetVolumeSnapshots));
// Can only boot image to volume if the Nova extension is enabled.
novaExtensions.ifNameEnabled('BlockDeviceMappingV2Boot')
.then(function(){ model.allowCreateVolumeFromImage = true; });
return $q.all(volumePromises);
}
function onGetVolumes(data) {
model.volumes.length = 0;
push.apply(model.volumes, data.data.items);
}
function onGetVolumeSnapshots(data) {
model.volumeSnapshots.length = 0;
push.apply(model.volumeSnapshots, data.data.items);
}
function addAllowedBootSource(rawTypes, type, label) {
if (rawTypes && rawTypes.length > 0) {
model.allowedBootSources.push({
type: type,
label: label
});
}
}
function setFinalSpecBootsource(finalSpec) {
finalSpec.source_id = finalSpec.source && finalSpec.source[0] && finalSpec.source[0].id;
delete finalSpec.source;
switch (finalSpec.source_type.type) {
case SOURCE_TYPE_IMAGE:
setFinalSpecBootImageToVolume(finalSpec);
break;
case SOURCE_TYPE_SNAPSHOT:
break;
case SOURCE_TYPE_VOLUME:
setFinalSpecBootFromVolumeDevice(finalSpec, 'vol');
break;
case SOURCE_TYPE_VOLUME_SNAPSHOT:
setFinalSpecBootFromVolumeDevice(finalSpec, 'snap');
break;
default:
// error condition
console.log("Unknown source type: " + finalSpec.source_type);
}
// The following are all fields gathered into simple fields by
// steps so that the view can simply bind to simple model attributes
// that are then transformed a single time to Nova's expectation
// at launch time.
delete finalSpec.source_type;
delete finalSpec.vol_create;
delete finalSpec.vol_device_name;
delete finalSpec.vol_delete_on_terminate;
delete finalSpec.vol_size;
}
function setFinalSpecBootImageToVolume(finalSpec){
if(finalSpec.vol_create) {
// Specify null to get Autoselection (not empty string)
var device_name = finalSpec.vol_device_name ? finalSpec.vol_device_name : null;
finalSpec.block_device_mapping_v2 = [];
finalSpec.block_device_mapping_v2.push(
{
'device_name': device_name,
'source_type': SOURCE_TYPE_IMAGE,
'destination_type': SOURCE_TYPE_VOLUME,
'delete_on_termination': finalSpec.vol_delete_on_terminate ? 1 : 0,
'uuid': finalSpec.source_id,
'boot_index': '0',
'volume_size': finalSpec.vol_size
}
);
}
}
function setFinalSpecBootFromVolumeDevice(finalSpec, sourceType) {
finalSpec.block_device_mapping = {};
finalSpec.block_device_mapping[finalSpec.vol_device_name] = [
finalSpec.source_id,
':',
sourceType,
'::',
(finalSpec.vol_delete_on_terminate ? 1 : 0)
].join('');
// Source ID must be empty for API
finalSpec.source_id = '';
}
// Nova Limits
function onGetNovaLimits(data) {
angular.extend(model.novaLimits, data.data);
}
// Metadata Definitions
/**
* Metadata definitions provide supplemental information in detail
* rows and should not slow down any of the other load processes.
* All code should be written to treat metadata definitions as
* optional, because they are never guaranteed to exist.
*/
function getMetadataDefinitions() {
// Metadata definitions often apply to multiple
// resource types. It is optimal to make a single
// request for all desired resource types.
var resourceTypes = {
flavor: 'OS::Nova::Flavor',
image: 'OS::Glance::Image',
volume: 'OS::Cinder::Volumes'
};
angular.forEach(resourceTypes, function (resourceType, key) {
glanceAPI.getNamespaces({
'resource_type': resourceType
}, true)
.then(function (data) {
var namespaces = data.data.items;
// This will ensure that the metaDefs model object remains
// unchanged until metadefs are fully loaded. Otherwise,
// partial results are loaded and can result in some odd
// display behavior.
if(namespaces.length) {
model.metadataDefs[key] = namespaces;
}
});
});
}
return model;
}
]);
})();
Status

The array or [] parameter is needed to specify dependent modules when you declare your own module, so this should only happen once per module.
The second notation, without the parameter is just retrieving the module so you can attach controllers/services/filters/... to it.
Use the array notation for the declaration of your module, use the single parameter notation if you want to add something to it.
For example:
in app.module.js
//You want to make use of the ngRoute module,
//so you have to specify a dependency on it
angular.module('app', ['ngRoute']);
You will only specify the dependencies on your module once, when you declare it.
in main.controller.js
//You want to add a controller to your module, so you want to retrieve your module
angular.module('app').controller('mainCtrl', mainCtrl);
function mainCtrl() { };
Now angular will try to find a module by that name instead of creating one, when it doesn't find one, you'll get some errors, which explains your original question.
You will typically do this every time you want to add something to your module.
Note that you could also achieve this by storing your module in a global variable when you create it and then access the module by that variable when you want to add things to it, however as you probably know, creating global variables is a bad practice.

Facing an error with angular is a bliss because it provides the link to description of the error in the console.
From an example page like that...
When defining a module with no module dependencies, the array of dependencies should be defined and empty.
var myApp = angular.module('myApp', []);
To retrieve a reference to the same module for further configuration, call angular.module without the array argument.
var myApp = angular.module('myApp');

Related

Run N/Task on multiple saved searches at once

Is there a straightforward way to modify the below code to execute on multiple saved searches and files simultaneously? I have all the file and search id's. but rather than creating 50 different scripts for each one can I just execute on a block of IDs by altering the below?
Thank you in advance.
All the Oracle documentation only seems to specify how to run these tasks on 1 item.
I know that a map/reduce script is a good path to go down for handling larger amounts of data, is that the way with this or can the below just be tweaked slightly?
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'],
/**
* #param {record} record
* #param {search} search
*/
function(task) {
var FILE_ID = 433961;
var SEARCH_ID = 1610;
function execute(scriptContext) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = SEARCH_ID;
searchTask.fileId = FILE_ID;
var searchTaskId = searchTask.submit();
}
return {
execute: execute
};
});
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'], function (task) {
const todos = [
{
FILE_ID: 433961,
SEARCH_ID: 1610
},
{
FILE_ID: '...',
SEARCH_ID: '...'
},
// ...
]
function execute(scriptContext) {
todos.forEach(function(todo) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = todo.SEARCH_ID;
searchTask.fileId = todo.FILE_ID;
var searchTaskId = searchTask.submit();
})
}
return {
execute: execute
};
});

What happens when I use angular.module(...).controller(...).directive(...)?And why? [duplicate]

This question already has answers here:
How to implement chained method calls like jQuery?
(4 answers)
Closed 5 years ago.
I think this equals to
var module = angular.module(...);
module.controller(...);
module.directive(...);
But I'm not sure. And I don't know what happens in angular and why I could write code this way.
I try to debug and trace it but it's so confused.
This is called a fluent API.
Each method will return the module instance, so that another method may be called.
To illustrate we can create a class that does something similar.
class Module {
controller() {
console.log('controller');
return this;
}
directive() {
console.log('directive');
return this;
}
}
When each method has finished, it will return the module instance this so that another method can be chained.
So now we can use this class and chain the methods like this:
new Module().controller().directive();
Or
const module = new Module();
module.controller();
module.directive();
What happens when I use angular.module(…).controller(…).directive(…)?And why?
Short answer
This is good way to write your code in one file.
If you want to split Angular project to different files, use 2nd approach:
var app = angular.module(...);
app.controller(...);
app.directive(...);
Long answer
Also take a look on this angular code snippets (took from https://code.angularjs.org/1.5.6/angular.js):
You can see controller, directive, module, filter, factory, value, provider, decorator, animation, config, component ,run returns moduleInstance
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** #type {Object.<string, angular.Module>} */
var modules = {};
return function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument.", name);
}
/** #type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** #type {!Array.<Function>} */
var configBlocks = [];
/** #type {!Array.<Function>} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
/** #type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_configBlocks: configBlocks,
_runBlocks: runBlocks,
requires: requires,
name: name,
provider: invokeLaterAndSetModuleName('$provide', 'provider'),
factory: invokeLaterAndSetModuleName('$provide', 'factory'),
service: invokeLaterAndSetModuleName('$provide', 'service'),
value: invokeLater('$provide', 'value'),
constant: invokeLater('$provide', 'constant', 'unshift'),
decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
config: config,
run: function(block) {
runBlocks.push(block);
return this;
}
};
if (configFn) {
config(configFn);
}
return moduleInstance;
function invokeLater(provider, method, insertMethod, queue) {
if (!queue) queue = invokeQueue;
return function() {
queue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
function invokeLaterAndSetModuleName(provider, method) {
return function(recipeName, factoryFunction) {
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
invokeQueue.push([provider, method, arguments]);
return moduleInstance;
};
}
});
};
});
}
Why is it better?
Both approaches do the same so developer will decide what is better for his project structure
for efficiency?
There is no efficiency value measurement, both has same efficiency. No performance penalty.
for what?
In project I want to write each directive each controller each ... in separate file so I use app.controller(...);, app.service(...); ,...
However common directives I want to put in one file so I use:
app.directive(…).directive(…).directive(…).directive(…).directive(…)
Hope it will spread the light on your understanding :)

Pass value from controller to factory to choose between objects

I have a quiz-application built in AngularJS in which I would like to choose between two objects, dependent on which quiz you choose in the view. When a quiz is selected in the view a function is executed. In this process I am sending a variable to the dataservice.js file that contain the objects and in here i have an if statement that chooses the right object depending in the input from the controller.
How do i use the quiz-variable outside of the changeState function? I want to use it to choose between the two quiz objects.
//controller
function activateQuiz(){
quizMetrics.changeState("quiz", true);
DataService.startQuiz("quiz_1");
//test("hund");
}
function activateQuiz_2(){
quizMetrics.changeState("quiz", true);
DataService.startQuiz("quiz_2");
//test("kat");
}
//Factory
(function(){
/*
* Declaring a factory service as part of the existing turtleFacts Module.
*/
angular
.module("turtleFacts")
.factory("DataService", DataService);
/*
* Actual definition of the function used for this factory
*/
function DataService(){
/*
* dataObj is used to simulate getting the data from a backend server
* The object will hold data which will then be returned to the other
* factory declared in js/factory/quiz.js which has this factory
* as a dependency
*/
var dataObj = {
startQuiz: startQuiz,
quizQuestions: quizQuestions,
correctAnswers: correctAnswers
};
var dataObj_2 = {
startQuiz: startQuiz,
quizQuestions: quizQuestions_2,
correctAnswers: correctAnswers_2
};
var quiz;
function startQuiz(metric, state){
if(metric === "quiz_1"){
quiz = "quiz1"
}else if(metric === "quiz_2"){
quiz = "quiz2"
}else{
return false;
}
console.log(quiz)
}
if (quiz = "quiz1") { // here i want to use the the quiz variable from the changeState function.
return dataObj;
} else if (quiz = "quiz2") {
return dataObj_2;
}
}
You could have something like this:
/*
* Declaring a factory service as part of the existing turtleFacts Module.
*/
angular
.module("turtleFacts")
.factory("DataService", DataService);
/*
* Actual definition of the function used for this factory
*/
function DataService(){
/*
* dataObj is used to simulate getting the data from a backend server
* The object will hold data which will then be returned to the other
* factory declared in js/factory/quiz.js which has this factory
* as a dependency
*/
var dataObj = {
changeState: changeState,
quizQuestions: quizQuestions,
correctAnswers: correctAnswers
};
var dataObj_2 = {
changeState: changeState,
quizQuestions: quizQuestions_2,
correctAnswers: correctAnswers_2
};
var quiz;
function changeState(metric, state){
if(metric === "quiz_1"){
quiz = "quiz1"
}else if(metric === "quiz_2"){
quiz = "quiz2"
}else{
return false;
}
console.log(quiz);
if (quiz == "quiz1") { // here i want to use the the quiz variable from the changeState function.
return dataObj;
} else if (quiz == "quiz2") {
return dataObj_2;
}
}
}
And from your controller, you can get the data as:
$scope.quiz = DataService. changeState(metric, state);

Limit http calls from a factory service

I'm using a factory service to fetch some data using the $http service. The problem here, I dont want to make a http request each time, I want to save this data somewhere and get a local copy of it when needed. To that end I thought creating an array inside that factory and assign the loaded data to it on the first call, and then just return it when required, instead of loading it again from the server. In my case, it the http service is fired every time. How can I fix this? I read here but that does not answer my question.
This is my factory:
angular.module("app").factory("getDataService", ['$http', function ($http) {
var usersArray = [];
if (usersArray.length === 0) {
return {
getJsonData: function () {
return $http.get('https://api.myjson.com/bins/eznv3')
.success(function (data, status, headers, config) {
usersArray = data;
return data;
})
.error(function (error, status, headers, config) {
});
}
}
}else{
return usersArray;
}
}]);
And this is the controller that uses this service:
angular.module("app").controller("ctrl", ["$scope", "getDataService", function ($scope, getDataService) {
angular.element(document).ready(function () {
getDataService.getJsonData().then(function (data) {
$scope.users = data.data;
});
});
}]);
You do not need to cache the response of $http.get manually, angularJS itself provides a way to cache the response. Try below code in your getJsonData function of your factory:
getJsonData: function () {
return $http.get('https://api.myjson.com/bins/eznv3', {cache: true})
.success(function (data, status, headers, config) {
return data;
})
.error(function (error, status, headers, config) {
});
}
Source: https://docs.angularjs.org/api/ng/service/$http#get
Read the above document. You will find configurations from there.
You can use Local Storage for it, one of the best and easiest ways.
LocalStorage.setItem('usersArray',data); sets the data in the local storage.
LocalStorage.getItem('usersArray'); retrieves the data from local storage.
Here is the change of your factory,
angular.module("app").factory("getDataService", ['$http', function ($http) {
var usersArray = LocalStorage.getItem('usersArray');
if (usersArray.length === 0) {
return {
getJsonData: function () {
return $http.get('https://api.myjson.com/bins/eznv3', {cache: true})
.success(function (data, status, headers, config) {
usersArray = data;
LocalStorage.setItem('usersArray',data);
return data;
})
.error(function (error, status, headers, config) {
});
}
}
}else{
return LocalStorage.getItem('usersArray');
}
}]);
Your controller,
angular.module("app").controller("ctrl", ["$scope", "getDataService", function ($scope, getDataService) {
var x = [];
angular.element(document).ready(function () {
if (x.length == 0) {
getDataService.getJsonData().then(function (data) {
x = data.data;
$scope.users = x;
});
}else{
console.log("local copy of data exists");
}
});
}]);
Advantages of localstorage:
With local storage, web applications can store data locally within the user's browser.
Unlike cookies, the storage limit is far larger (at least 5MB) and information is never transferred to the server.
Few days back, i got same kind of requirement and following is code of module i had created for same...
'use strict';
(function() {
angular.module('httpService', []).service("api", ["$http", "dbService", function($http, dbService) {
/**
* <Pankaj Badukale>
* ()
* request.url => Url to request
* request.method => request method
* request.data => request data
* request.mask => This is custom object for out use
*
* #return ()
*/
return function (request) {
var url = (request != undefined && request.url != undefined) ? request.url : "./";
var method = (request != undefined && request.method != undefined) ? request.method : "GET";
var rData = (request != undefined && request.data != undefined) ? request.data : {};
/**
* mask is CUSTOME object we add to request object
* Which is useful for keep track of each request as well interceptor execute
*
* IT HAS
* {
* save : true, //tell that save request response in session
* fetch : true, //check local data first,
* fetchSource : tell about perticular source of data DEFAULT WILL BE sessionStorage
* OPTIONS are session and local
* } strucutre FOR NOW may be it will better or enhance in future
*
* message property to set message in alert
* doExecute tell wheather you want to execute maskMan code for this request
*
* while saving and fetching data from local it uses URL of request as key
* maskMan is a factory which iterate your error response object and we can add different behaviours for maskMan
*/
var mask = {};
if(request != undefined && request.mask != undefined) {
mask = request.mask;
}
return dbService.http(request).then(function(data) {
console.log("Data fetched from local "+ request.url);
return data;
}, function(err) {
return $http({
url: url,
method: method,
data: rData,
mask: mask,
header:{
'content-type':'application/json'
}
}).then(function(response) {
return response.data;
},function(error) {
return error;
});
});
};
}]).service('customHttpInterceptor', ["$q", "maskMan", function($q, maskMan) {
return {
//before send request to server
request: function(config) {
return config;
},
//if any found in request object
requestError: function(rejection) {
return $q.reject(rejection);
},
//on response come to web app
response: function(response) {
maskMan.responseIterator(response);
//you to return any thing as response from here
return response;
},
//if there is error in response`
responseError: function(rejection) {
maskMan.statusIterator(rejection);
return $q.reject(rejection);
}
};
}]).factory("maskMan", ["dbService", function(dbService) {
return {
/**
* statusIterator
* Iterate response object on error comes
*/
statusIterator: function(rejection) {
if( rejection.config.mask.doExecute == true) {
switch(rejection.status) {
case 404: this.notFound(rejection);
break;
default: this.dontKnow(rejection);
}
}
},
/**
* notFound
* Function to defined logic for 404 error code scenario's
* Here we can defined generic as well specific request object conditions also
*/
notFound: function(rejection) {
var errMsg = rejection.config.mask.message || "Something wrong";
alert(errMsg);
rejection.stopExecute = true;//stop further execute of code flag
},
/**
* dontKnow
* For every error response this method goingt to envoke by default
*/
dontKnow: function(maskObject) {
console.log("Don't know what to do for "+maskObject.config.url);
},
/**
* responseIterator
* Define logic to do after response come to browser
*
* #params JSON resp
*/
responseIterator: function(resp) {
//Logic to save data of response in session storage with mask command save
if( resp.config.mask !== undefined && resp.config.mask.save === true ) {
var sdata = JSON.stringify(resp.data);
var skey = resp.config.url;
dbService.sinsert(skey, sdata);
}//END
}
};
}]).service("dbService", ["$q", function($q) {
/**
* http
* Custom mirror promise to handle local storage options with http
*
* #params JSON request
*/
this.http = function(request) {
var self = this;
return $q(function(resolve, reject) {
if( request.mask != undefined && request.mask.fetch === true ) {
var data = null;
if( request.mask.fetchSource == undefined || request.mask.fetchSource == "session") {//go for default sessionStorage
data = JSON.parse(self.sget(request.url));
} else if( request.mask.fetchSource == "local" ) {
data = JSON.parse(self.get(request.url));
} else {
reject( "Fetch source is not defined." );
}
if( data != undefined && data != null ) {
resolve(data);
} else {
reject("Data not saved in local "+request.url);
}
} else {
reject("Data not saved in local "+request.url);
}
});
}
/**
* Add/Override data to local storage
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.insert = function(key, data, callback) {
localStorage.setItem(key, data);
if( callback != undefined ) {
callback();
} else {
return true;
}
}
/**
* Update data of local storage
* This function generally used to data which is already exist and need to update
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.update = function(key, data, callback) {
var self = this;
self.view(key, function(localData) {//callback function
if( localData != undefined && localData != null ) {
//already some data exist on this key So need to update it
data = localData.push(data);
}
//just handover to insert
if( callback !== undefined ) {
self.insert(key, data, callback);
} else {
return self.insert(key, data);
}
});
}
/**
* Remove data from local storage on basis of key
*
* #params String key
* #return Boolean
*/
this.remove = function(key, callback) {
localStorage.removeItem(key);
if( callback !== undefined ) {
callback();
} else {
return true;
}
}
/**
* Get key data of local storage
* #param String key
*
* #return Array data WHEN all data OR
* #return String data WHEN key value
*/
this.get = function(key, callback) {
var key = key || "";
var data = [];
if( key == "" ) {
//get all data
for(var i in localStorage) {
data.push(JSON.parse(localStorage[i]));
}
} else {
//get one key data
data = localStorage.getItem(key);
}
if(callback != undefined) {
callback(data);
} else {
return data;
}
}
/**
* sinsert
* Add/Override data to session storage
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.sinsert = function(key, data, callback) {
var key = this.encode(key);
sessionStorage.setItem(key, data);
if( callback != undefined ) {
callback();
} else {
return true;
}
}
/**
* supdate
* Update data of session storage
* This function generally used to data which is already exist and need to update
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.supdate = function(key, data, callback) {
var self = this;
self.view(key, function(localData) {//callback function
if( localData != undefined && localData != null ) {
//already some data exist on this key So need to update it
data = localData.push(data);
}
//just handover to insert
if( callback !== undefined ) {
self.insert(key, data, callback);
} else {
return self.insert(key, data);
}
});
}
/**
* sremove
* Remove data from session storage on basis of key
*
* #params String key
* #return Boolean
*/
this.sremove = function(key, callback) {
var key = this.encode(key);
sessionStorage.removeItem(key);
if( callback !== undefined ) {
callback();
} else {
return true;
}
}
/**
* get
* Get key data of session storage
* #param String key
*
* #return Array data WHEN all data OR
* #return String data WHEN key value
*/
this.sget = function(key, callback) {
var key = key || "";
var data = [];
if( key == "" ) {
//get all data
for(var i in sessionStorage) {
data.push(JSON.parse(sessionStorage[i]));
}
} else {
//get one key data
key = this.encode(key);
data = sessionStorage.getItem(key);
}
if(callback != undefined) {
callback(data);
} else {
return data;
}
}
/**
* encode
* encode give string using javascript
*
* #param String str
* #return String
*/
this.encode = function(str) {
return btoa(str);
}
/**
* decode
* decode give string using javascript
*
* #param String str
* #return String
*/
this.decode = function(str) {
return atob(str);
}
return this;
}]).config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('customHttpInterceptor');
}]);
})();
How to use it::
Include this module in you project....
Then use "httpService" always for http requests all for API calls...
We need to pass config object to this service tell about API call and what should do with it....You can find details about config in code itself...
So how to use in controller..
module.controller('nameofController', ['httpService', function(httpService) {
httpService({
url: 'Your API url',
method: 'GET',
mask: {
save : true, //tell that save request response in session
fetch : true, //check local data first before next fetch,
fetchSource : tell about perticular source of data DEFAULT WILL BE sessionStorage OPTIONS are session and local
}
}).then(function(data) {
// promise is all same as $http
console.log(data);
});
}]);
Hope this will help... You can go with very simple solution as well to just mark
{cache: true}
...
But this solution is completely customized and under all controls
Original code which has used in production is at gist

Underscore.js groupBy multiple values

Using Underscore.js, I'm trying to group a list of items multiple times, ie
Group by SIZE then for each SIZE, group by CATEGORY...
http://jsfiddle.net/rickysullivan/WTtXP/1/
Ideally, I'd like to have a function or extend _.groupBy() so that you can throw an array at it with the paramaters to group by.
var multiGroup = ['size', 'category'];
Probably could just make a mixin...
_.mixin({
groupByMulti: function(obj, val, arr) {
var result = {};
var iterator = typeof val == 'function' ? val : function(obj) {
return obj[val];
};
_.each(arr, function(arrvalue, arrIndex) {
_.each(obj, function(value, objIndex) {
var key = iterator(value, objIndex);
var arrresults = obj[objIndex][arrvalue];
if (_.has(value, arrvalue))
(result[arrIndex] || (result[arrIndex] = [])).push(value);
My head hurts, but I think some more pushing needs to go here...
});
})
return result;
}
});
properties = _.groupByMulti(properties, function(item) {
var testVal = item["size"];
if (parseFloat(testVal)) {
testVal = parseFloat(item["size"])
}
return testVal
}, multiGroup);
A simple recursive implementation:
_.mixin({
/*
* #mixin
*
* Splits a collection into sets, grouped by the result of running each value
* through iteratee. If iteratee is a string instead of a function, groups by
* the property named by iteratee on each of the values.
*
* #param {array|object} list - The collection to iterate over.
* #param {(string|function)[]} values - The iteratees to transform keys.
* #param {object=} context - The values are bound to the context object.
*
* #returns {Object} - Returns the composed aggregate object.
*/
groupByMulti: function(list, values, context) {
if (!values.length) {
return list;
}
var byFirst = _.groupBy(list, values[0], context),
rest = values.slice(1);
for (var prop in byFirst) {
byFirst[prop] = _.groupByMulti(byFirst[prop], rest, context);
}
return byFirst;
}
});
Demo in your jsfiddle
I think #Bergi's answer can be streamlined a bit by utilizing Lo-Dash's mapValues (for mapping functions over object values). It allows us to group the entries in an array by multiple keys in a nested fashion:
_ = require('lodash');
var _.nest = function (collection, keys) {
if (!keys.length) {
return collection;
}
else {
return _(collection).groupBy(keys[0]).mapValues(function(values) {
return nest(values, keys.slice(1));
}).value();
}
};
I renamed the method to nest because it ends up serving much the same role served by D3's nest operator. See this gist for details and this fiddle for demonstrated usage with your example.
lodash nest groupby
How about this rather simple hack?
console.log(_.groupBy(getProperties(), function(record){
return (record.size+record.category);
}));
Check out this underscore extension: Underscore.Nest, by Irene Ros.
This extension's output will be slightly different from what you specify, but the module is only about 100 lines of code, so you should be able to scan to get direction.
This is a great use case for the reduce phase of map-reduce. It's not going to be as visually elegant as the multi-group function (you can't just pass in an array of keys to group on), but overall this pattern gives you more flexibility to transform your data. EXAMPLE
var grouped = _.reduce(
properties,
function(buckets, property) {
// Find the correct bucket for the property
var bucket = _.findWhere(buckets, {size: property.size, category: property.category});
// Create a new bucket if needed.
if (!bucket) {
bucket = {
size: property.size,
category: property.category,
items: []
};
buckets.push(bucket);
}
// Add the property to the correct bucket
bucket.items.push(property);
return buckets;
},
[] // The starting buckets
);
console.log(grouped)
But if you just want it in an underscore mixin, here's my stab at it:
_.mixin({
'groupAndSort': function (items, sortList) {
var grouped = _.reduce(
items,
function (buckets, item) {
var searchCriteria = {};
_.each(sortList, function (searchProperty) { searchCriteria[searchProperty] = item[searchProperty]; });
var bucket = _.findWhere(buckets, searchCriteria);
if (!bucket) {
bucket = {};
_.each(sortList, function (property) { bucket[property] = item[property]; });
bucket._items = [];
buckets.push(bucket);
}
bucket._items.push(item);
return buckets;
},
[] // Initial buckets
);
grouped.sort(function (x, y) {
for (var i in sortList) {
var property = sortList[i];
if (x[property] != y[property])
return x[property] > y[property] ? 1 : -1;
}
return 0;
});
return _.map(grouped, function (group) {
var toReturn = { key: {}, value: group.__items };
_.each(sortList, function (searchProperty) { toReturn.key[searchProperty] = group[searchProperty]; });
return toReturn;
});
});
The improvements by joyrexus on bergi's method don't take advantage of the underscore/lodash mixin system. Here it is as a mixin:
_.mixin({
nest: function (collection, keys) {
if (!keys.length) {
return collection;
} else {
return _(collection).groupBy(keys[0]).mapValues(function(values) {
return _.nest(values, keys.slice(1));
}).value();
}
}
});
An example with lodash and mixin
_.mixin({
'groupByMulti': function (collection, keys) {
if (!keys.length) {
return collection;
} else {
return _.mapValues(_.groupBy(collection,_.first(keys)),function(values) {
return _.groupByMulti(values, _.rest(keys));
});
}
}
});
Here is an easy to understand function.
function mixin(list, properties){
function grouper(i, list){
if(i < properties.length){
var group = _.groupBy(list, function(item){
var value = item[properties[i]];
delete item[properties[i]];
return value;
});
_.keys(group).forEach(function(key){
group[key] = grouper(i+1, group[key]);
});
return group;
}else{
return list;
}
}
return grouper(0, list);
}
Grouping by a composite key tends to work better for me in most situations:
const groups = _.groupByComposite(myList, ['size', 'category']);
Demo using OP's fiddle
Mixin
_.mixin({
/*
* #groupByComposite
*
* Groups an array of objects by multiple properties. Uses _.groupBy under the covers,
* to group by a composite key, generated from the list of provided keys.
*
* #param {Object[]} collection - the array of objects.
* #param {string[]} keys - one or more property names to group by.
* #param {string} [delimiter=-] - a delimiter used in the creation of the composite key.
*
* #returns {Object} - the composed aggregate object.
*/
groupByComposite: (collection, keys, delimiter = '-') =>
_.groupBy(collection, (item) => {
const compositeKey = [];
_.each(keys, key => compositeKey.push(item[key]));
return compositeKey.join(delimiter);
}),
});

Resources