AngularJS Circular Dependency - Transformers - angularjs

looking for a bit of advice if possible.
In dealing with an API and nested resources, I'm trying to make a set of "transformers" which format the data into a more usable format.
In doing this, I have to create a circular dependency, due to the nature of the nested resources, and that the relationships could be going each way.
Here's an example:
Client Transformer
angular.module('services.transformers.clients', [
])
.service('ClientTransformer', function ClientTransformer(DateService, EventTransformer){
var transformer = this;
transformer.transform = function(client) {
clientObject = {
id: client.id,
name: client.name
};
if(client.events) {
clientObject.events = includeEvents(client);
}
return clientObject;
}
transformer.transformCollection = function(clients) {
for(var key in clients.data) {
if(clients.data.hasOwnProperty(key)) {
clients.data[key] = transformer.transform(clients.data[key]);
}
}
return clients.data;
}
function includeEvents (client) {
return EventTransformer.transformCollection(client.events);
}
})
;
Event Transformer
angular.module('services.transformers.events', [
])
.service('EventTransformer', function EventTransformer(DateService, ClientTransformer){
var transformer = this;
transformer.transform = function(event) {
eventObject = {
id: event.id,
title: event.title,
description: event.description,
client: includeClient(event),
starts: DateService.createDateFromObject(event.starts),
ends: DateService.createDateFromObject(event.ends)
};
return eventObject;
}
transformer.transformCollection = function(events) {
for(var key in events.data) {
if(events.data.hasOwnProperty(key)) {
events.data[key] = transformer.transform(events.data[key]);
}
}
return events.data;
}
function includeClient (event) {
// check if client is an ID or nested data. If nested, transform then return
return (event.client.data) ? ClientTransformer.transform(event.client.data) : event.client;
}
})
;
As you can imagine, there are several more relationships off each of these resources, but these services need to depend on each other. I could be accessing the Event resource, and have nested Clients, or Vice Versa.
Angular really doesn't like this, and throws a circular dependency error, but I'm not sure how I can get around this, or the better way to structure my application.
Any help would be appreciated
Thanks!

You can inject the $injector service and get the dependency when you actually use it.
Here is your workaround:
.service('EventTransformer', function EventTransformer(DateService, $injector){
...
function includeClient (event) {
var t = $injector.get('ClientTransformer');
return (event.client.data) ? t.transform(event.client.data) : event.client;
}
})

Related

Angular 7/Typescript : Create queue/array of methods

I have a requirements that some functions should be called after some method completes execution.
Below is my code of processing the queue.
processQueue() {
while (this.queue.length) {
var item = this.queue.shift();
item.resolve(item.func(item.types));
}
}
This is one of the sample function to push method in queue
getAllValues() {
let promise1 = new Promise((resolve, reject) => {
if (this.isReady) {
resolve(this._getAllValues());
} else {
this.queue.push({
resolve: resolve,
func: this._getAllValues
});
}
});
return promise1;
}
And this is one of the function which will be called on processing the queue
_getAllValues() {
var results = {}, values = this.enumInstance.enumsCache.values;
for (var type in values) {
if (values.hasOwnProperty(type)) {
results[type] = values[type][this.enumInstance.lang];
}
}
return results;
}
The issue i am facing is when i call _getAllValues() directly then i am able to access this.enumInstance.
But when same method is being accessed through processQueue() i am unable to access this.enumInstance. It gives me undefined. I think this is not referred to main class in this case.
So can anyone help me here. How can i resolve this?

Angular - Organise controller, factory and "class"

I would like to understand how to have a nice organisation in my angular project.
[see code below]
Does it makes sense to have the getFireList function into the Factory ? Or should i put it into the controller ?
Does the "class" Fire makes sense ? Should i remove it ? Should i move it to the controller ? Should i move it the the factory ?
If you see anything wrong in this code i'm really interested to learn more.
For now, i've got this :
A class "Fire" to create new object of type Fire.
function Fire (p_power) {
// ATTRIBUTES
this.id = null;
this.power = p_power;
this.position = {
x: null,
y: null
}
// GETTERS/SETTERS
// id
this.getId = function() {
return this.id;
}
this.setId = function(p_id) {
this.id = p_id;
}
// power
this.getPower = function() {
return this.power;
}
this.setPower = function(p_power) {
this.power = p_power;
}
// position
this.getPosition = function() {
return this.position;
}
this.setPosition = function(p_position) {
this.position = p_position;
}
// METHODS
this.increasePower = function(p_plus) {
this.power += p_plus;
}
this.decreasePower = function(p_minus) {
this.power -= p_minus;
}
}
A controller
simuApp.controller('FireController', function($scope, FireFactory) {
// ...
});
And a factory
simuApp.factory('FireFactory', function() {
return {
fire_list: [],
getFireList : function() {
return $http.get(site_url+'fire/fireList').
then(
function(success) {
var data = success.data;
var fires = [];
var fire_tmp;
for (i=0 ; i<data.length ; i++) {
fire_tmp = new Fire( data[i].power );
fire_tmp.setId( data[i].idFire );
fires.push( fire_tmp );
}
fire_list = fires;
return fire_list;
}, function(err) {
// ...
}
);
}
}
});
Thanks for your help.
First, let's get the terminology right. .factory is a method to register a function that generates an instance of the service - hence "factory". What it generates, though, is a singleton service instance.
So, the service you create would be more properly named as FireSvc (as opposed to FireFactory), whereas the function that creates it could have the word "factory" in it (although, in the case below, that function name is not really needed - it could just be an anonymous function):
.factory("FireSvc", function FireSvcFactory(){
});
It is a good practice to use a Service to abstract away any domain/business logic from the controller. Keep the controller thin, responsible only to define the ViewModel, and react to events by changing the ViewModel or invoking functions on the Model.
So, having FireSvc.getFireList() makes sense.
Now, whether the list is a collection of plain objects, or instances of Fire is completely independent of Angular and is entirely up to you. In any case, it is too broad of a topic to discuss in a SO answer.

Breeze 1-m-1 in HotTowel Angular with local storage

I've had a requirement recently to implement a UI for managing a many-many relationship. Ward Bell kindly provided this plunker showing how to implement using 1-m-1 with Angular and Breeze.
My app's design is based largely (especially the datacontext and the local storage) is based largely on John Papa's recent Pluralsight courses.
In my app, BusUnit = Hero and Dimension = Power (in reference to Ward's example.
Everything seems to be working well when I force the app to fetch data from the server, in that my updates to a business unit's dimensions reflect correctly. The problem I'm facing now is when I navigate away from the page and back again (which gets data from local storage). In this case:
if I previously added a new dimension to a business unit, everything is ok, but
if i previously marked a business unit's dimension for deletion and the save, the dimension still appears for the business unit in question.
this is the controller code that initially gets business units and their dimensions:
function getdboardStructure() {
var busUnitsPromise = datacontextSvc.busUnits.getByDboardConfigId(vm.dboardConfig.id);
var dimensionsPromise = datacontextSvc.dimensions.getByDboardConfigId(vm.dboardConfig.id);
$q.all([busUnitsPromise, dimensionsPromise])
.then(function (values) {
vm.busUnits = values[0];
vm.dims = values[1];
createBusUnitVms();
//vm.currentBusUnitVm = vm.busUnitVms[0]; // not required as using accordion instead of drop-down
vm.hasChanges = false;
});
}
this is the code in my controller that prepares for the save:
function applyBusUnitDimensionSelections(busUnitVm) {
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimensionMapVms;
var dimensionHash = createBusUnitDimensionHash(busUnit);
mapVms.forEach(function (mapVm) {
var map = dimensionHash[mapVm.dimension.id];
if (mapVm.selected) {
if (!map) {
datacontextSvc.busUnits.addBusUnitDimension(busUnit, mapVm.dimension)
.then(function () {
});
}
} else {
if (map) {
datacontextSvc.markDeleted(map);
}
}
});
}
this is the code in my controller that executes the save:
function save() {
if (!canSave()) {
return $q.when(null);
}
vm.isSaving = true;
vm.busUnitVms.forEach(applyBusUnitDimensionSelections);
return datacontextSvc.save().then(function (saveResult) {
vm.isSaving = false;
trapSavedDboardConfigId(saveResult); // not relevant to use case
}, function (error) {
vm.isSaving = false;
});
}
this is the code in my repository that add a new busUnitDimension entity:
function addBusUnitDimension(busUnit, dimension) {
var newBusUnitDimension = this.em.createEntity(busUnitDimension);
newBusUnitDimension.busUnitId = busUnit.id;
newBusUnitDimension.dimensionId = dimension.id;
return this.$q.when(newBusUnitDimension);
}
this is my datacontext code for marking an item deleted:
function markDeleted(entity) {
return entity.entityAspect.setDeleted();
}
and finally this is the repository code to get business units and their join table entities:
function getByDboardConfigId(dboardConfigId, forceRefresh) {
var self = this;
var predicate = pred.create('dboardConfigId', '==', dboardConfigId);
var busUnits;
if (self.zStorage.areItemsLoaded('busUnits') && !forceRefresh) {
busUnits = self._getAllLocal(entityName, orderBy, predicate);
return self.$q.when(busUnits);
}
return eq.from('BusUnits')
.expand('BusUnitDimensions')
.where(predicate)
.orderBy(orderBy)
.using(self.em).execute()
.to$q(succeeded, self._failed);
function succeeded(data) {
busUnits = data.results;
self.zStorage.areItemsLoaded('busUnits', true);
self.zStorage.save();
//self.logSuccess('Retrieved ' + busUnits.length + ' business units from server', busUnits.length, true);
return busUnits;
}
}
My departure from John's course examples is that I'm using expand in the function I use to get Business Units from the server, and my hypothesis is that this has something to do with the fact that breeze is going to the server everytime I refresh the page (without clearing cache) instead, and that this also has something to do with the error i'm receiving if I navigate away and then back to the page.
Can anyone offer and suggestions?
Appreciate this was a long time ago and you have probably solved it or moved on but I came up against the same problem recently that took me ages to resolve.
The answer I found is that you have to edit JP's angular.breeze.storagewip.js file.
I contains the names of the entities hard-coded into the file and you will need to change these to match your own entities.
There are two functions where you need to do this, examples below show the changes with the four entities I am using:
function zStorageCore($rootScope, zStorageConfig) {
var storeConfig = zStorageConfig.config;
var storeMeta = {
breezeVersion: breeze.version,
appVersion: storeConfig.version,
isLoaded: {
elementAssets : false,
surveyors : false,
elements : false,
assets : false
}
};
and...
function checkStoreImportVersionAndParseData(importedData) {
if (!importedData) {
return importedData;
}
try {
var data = JSON.parse(importedData);
var importMeta = data[0];
if (importMeta.breezeVersion === storeMeta.breezeVersion &&
importMeta.appVersion === storeMeta.appVersion) {
if (importMeta.isLoaded) {
storeMeta.isLoaded.assets = storeMeta.isLoaded.assets || importMeta.isLoaded.assets;
storeMeta.isLoaded.elements = storeMeta.isLoaded.elements || importMeta.isLoaded.elements;
storeMeta.isLoaded.surveyors = storeMeta.isLoaded.surveyors || importMeta.isLoaded.surveyors;
storeMeta.isLoaded.elementAssets = storeMeta.isLoaded.elementAssets || importMeta.isLoaded.elementAssets;
}
return data[1];
} else {
_broadcast(storeConfig.events.error,
'Did not load from storage because mismatched versions',
{ current: storeMeta, storage: importMeta });
}
} catch (ex) {
_broadcast(storeConfig.events.error, 'Exception during load from storage: ' + ex.message, ex);
}
return null; // failed
}
I solved this by comparing JP's Style Guide course files with his SPA/Angular/Breeze course.

AngularJS. Return new factory instance

I'm a newbie in AngularJS and have faced the issue.
Can I reinject my factory singleton object across all controllers, where it's been injected?
For example:
.factory('medicalCenterService', function(MedicalCenterResource) {
var medicalCenterService = {};
medicalCenterService.currentMedCenter = MedicalCenterResource.get();
medicalCenterService.reloadMedCenter = function() {
medicalCenterService.currentMedCenter = MedicalCenterResource.get();
return medicalCenterService.currentMedCenter;
};
medicalCenterService.updateMedicalCenter = function(medicalCenter) {
MedicalCenterResource.updateMedicalCenter(medicalCenter);
medicalCenterService.currentMedCenter = medicalCenter;
};
return medicalCenterService;
})
In MedicalCenterController I get singleton object with medical center when application starts:
function MedicalCenterController($scope, medicalCenterService) {
$scope.currentMedCenter = medicalCenterService.currentMedCenter;
}
But later I try to edit medical center fields (name, address, etc..) in AccountProfileController
function AccountProfileController($scope, medicalCenterService) {
$scope.currentMedCenter = medicalCenterService.currentMedCenter;
$scope.applyMedCenterChanges = function (currentMedCenter) {
medicalCenterService.updateMedicalCenter(currentMedCenter);
};
}
And what I'm expecting to have is the object with updated fields.
How to return a new instance of my singleton?
Do you want something like this?
.factory('MedicalCenter', function(MedicalCenterResource) {
var MedicalCenter = function () {
var center = MedicalCenterResource.get(),
update = function() {
MedicalCenterResource.updateMedicalCenter(center)
};
return {
center: center,
update: update
}
};
return MedicalCenter;
})
function MedicalCenterController($scope, MedicalCenter) {
center = new MedicalCenter();
$scope.currentMedCenter = center.center;
}
function AccountProfileController($scope, MedicalCenter) {
center = new MedicalCenter();
$scope.currentMedCenter = center.center;
$scope.applyMedCenterChanges = function () {
center.update();
};
}
Like you wrote in post services are Singletons and its good way to share data over services. However if you want to create new instance of factory/service, you can't do that but we can create list of objects in one service/factory where each list item represents different instance. Something like:
.factory('medicalCenterService', function(MedicalCenterResource) {
var medicalCenterServices = [
{ctrlName: 'MedicalCenterController',medicalCenterService: {/*....*/}},
{ctrlName: 'AccountProfileController',medicalCenterService: {/*....*/}},
];
//......
})

Fetch a Backbone.Collection made up of other collections

So I have a few types of data:
Post
Project
Event
And each of those data models have their own collection and a route to view them:
/posts => app.postsCollection
/projects => app.projectsCollection
/events => app.eventsCollection
Now I want to add another route:
/ => app.everythingCollection
How can I create a collection which displays an aggregate of the other three collections, but without fetching all the post project and event data again?
Similarly, calling everythingCollection.fetch() would fill the postsCollection, projectsCollection and eventsCollection so that their data was available when they were rendered independently.
The whole point being never to download the same data twice.
Your app.everythingCollection doesn't have to be a really backbone collection. All it needs is just access and fetch to other collections.
You can inherit the Backbone.Events to gain all the events facilities also.
var fetchedRecords = {posts: 0, projects: 0, events: 0};
var Everything = function () {}
_.extend(Everything.prototype, Backbone.Events, {
fetch: function (option) {
that = this;
this.count = 0;
option.success = function () {that.doneFetch(arguments)};
if (fetchRecords.posts == 0) {
option.fetchedName = "posts";
app.postsCollection.fetch(option);
this.count ++;
}
if (fetchRecords.projects == 0) {
option.fetchedName = "projects";
app.projectsCollection.fetch(option);
this.count ++;
}
if (fetchRecords.events == 0) {
option.fetchedName = "events";
app.eventsCollection.fetch(option);
this.count ++;
}
},
donefetch: function (collection, response, options) {
if (this.count <=0) return;
this.count --;
if (this.count == 0) {
if (options.reset) this.trigger("reset");
}
fetchedRecords[options.fetchedName] ++;
},
posts: function () {return app.postsCollection},
projects: function () {return app.projectsCollection},
events: function () {return app.eventsCollection}
});
app.everythingCollection = new Everything;
everythingView.listenOn app.everythingCollection, "reset", everythingView.render;
app.everythingCollection.fetch({reset: true});
You will need to increment fetchedRecrods count to prevent fetch multiple times.
Something like this. Code is untested. But idea is the same.
var EverythingCollection = Backbone.Model.extend({
customFetch: function (){
var collections = [app.postsCollection, app.projectsCollection, app.eventsCollection],
index = -1,
collection,
that = this;
this.reset(); //clear everything collection.
//this function check collections one by one whether they have data or not. If collection don't have any data, go and fetch it.
function checkCollection() {
if (index >= collections.length) { //at this point all collections have data.
fillEverything();
return;
}
index = index + 1;
collection = collections[index];
if (collection && collection.models.length === 0) { //if no data in collection.
collection.fetch({success: function () {
checkCollection();
}});
} else { //if collection have data already, go to next collection.
checkCollection();
}
}
function fillEverything() {
collections.forEach(function (collection) {
if (collection) {
that.add(collection.models); //refer this http://backbonejs.org/#Collection-add
}
});
}
}
});
use like below.
app.everythingCollection = new EverythingCollection();
app.everythingCollection.customFetch();
for other collections, check models length before fetch data. Something like below.
if (app.postsCollection.models.length === 0) {
app.postsCollection.fetch();
}
Store all necessary collections in an array or object at app startup, attach an event listener to each of them listening for the first reset event and remember the ones you fetched in a second array. If the route where you need all collections is used you can fetch the ones not found in the array for the already fetched collections:
(untested, but it will give you the idea of how i suppose to do it)
var allCollections = [app.postsCollection, app.projectsCollection, app.eventsCollection];
var fetchedCollections = [];
$.each(allCollection, function(i, coll){
coll.once("reset", function(){
fetchedCollections.push(coll);
})
});
var fetchAll = function(){
$.each(allCollections, function(i, coll){
if( $.inArray(coll, fetchedCollections ) == -1 ){
coll.fetch();
}
});
}
Do this in your everythingCollection and you have the everythingCollection.fetchAll() functionality you need. You could also override the fetch function of the everythingCollection to first fetch all other collections:
fetch(options){
this.fetchAll();
return Backbone.Collection.prototype.fetch.call(this, options);
}
It sounds like braddunbar's supermodel or benvinegar's backbone.uniquemodel might address your problem
It's also worth checking out Soundcloud's article (see Sharing Models Between Views) on building Soundcloud next. They have a similar approach to the above two plugins in solving this problem.

Resources