Lodash & Angular: Transform JSON object into view model - javascript

I am trying to find the best way to transform a large JSON object into a view model. Previously, I had the model incorporated into the view, which is a bad practice. So, now I have the model being generated inside of a controller. I am using Lodash as a utility library.
My current design plan is to transform the JSON object into a "master" array that is accessible in the controller's scope. The JSON file is being served by Express. ModelService simply gets this file to make it available in the controller.
$scope.arr is the "master" array that I want to use in the view.
I also made the JSON data available for viewing at an external link since it is so large. Here it is.
(function() {
'use strict';
angular
.module('app')
.controller('ModelController', ModelController);
function ModelController($scope, ModelService, _) {
$scope.jsonData = ModelService.getAll();
$scope.getData = function() {
$scope.jsonData.$promise.then(function(data) {
$scope.all = data;
$scope.arr = [];
_.mapValues($scope.all.parent1.clusters, function(cluster) {
$scope.arr.push(cluster.name);
_.mapValues(cluster.subclusters, function(subcluster) {
$scope.arr.push(subcluster.name);
_.mapValues(subcluster.entities, function(entity) {
// map entities
})
});
});
});
};
$scope.getData();
}
})();
This code is just adding cluster and subcluster names to the array. I'd like the subclusters to be mapped to their parent cluster. The idea I have for doing this involves transforming each cluster element into its own array, and then adding the subclusters, and then transforming each subcluster into an array in order to map the entities to them. This seems tedious and inefficient. So, I am looking for a better way to achieve this.
It would be nice if I could add each cluster object to the array in one fell swoop without all the mapping and converting objects to arrays. Is that possible at all?
The wireframe view looks like this. The Flex Cluster Title is the name of the subcluster, and each number inside of them is an entity.

Firstly, I would move this processing into the service. It's easier to test, and keeps your view separated from your models (Controllers, are really more part of the "View" IMO when it comes to Angular especially if you're considering upgrading to Angular 2.0 in the future).
In Angular, I think the appropriate way to solve this, would be to use components (or directives) combined with ng-repeat.
The page template:
<!-- Page template, assume $ctrl is your controller, $ctrl.clusters is the data -->
<cluster ng-repeat = "cluster in $ctrl.clusters"
cluster-data="cluster" >
</cluster>
The cluster directive template:
<!-- Assume $ctrl is the controller for the cluster directive, $ctrl.cluster is the cluster object. -->
<div class="name">{{$ctrl.cluster}}</div>
<div class="subClusterNames"
ng-repeat="subCluster in $ctrl.cluster.subClusters>
{{subCluster.name}}
</div>
You might think that this is mapping your data too closely to the view, but as long as you use components to display your data (ie, don't put it all into one template) I think you'll be fine.

Related

Angular JS Concept 'Separation Of Concerns' --> CRUD Screen

I have a two CRUD screens in AngularJS.
1) Separate html file for View, Add and Edit. View Controller, Add Controller and Edit Controllers also Separate. This structure creating more duplicate code in html and controller side.
2) Separate html file for View, Add. View Controller, Add Controller only Separate. During edit mode I'm using a Boolean in controller to find its in edit mode or not.
I'm new to AngularJS. Anybody clearly tell me pros and cons, which way is correct in AngularJS 'Separation Of Concerns' concept.
I'm not sure you're going to the good direction.
By separation of concern, what is intended is to split the code that is responsible for managing the view, and the code responsible to make calls to your server. Imagine you want to make CRUD around a person, I would do the following :
service :
angular.module('app').factory("personService", ["$http", function($http)]){
return {
create : create,
update: update,
remove : remove,
get: get
};
function create(person){
return http.post("person/create", person);
}
// other functions
}
Then, I would only have 1 controller for everything :
angular.module("app").controller("PersonController", ["personService", function(personService)]){
var self = this;
self.isUpdate = true; // Insert logic here
self.isCreate = false; // Insert logic here
init();
self.save = function(){
var promise = self.isCreate ?
personService.create(self.person)
:personService.update(self.person);
promise.then(function(result){
// Handle return of save;
});
};
function init(){
if (!self.isCreate){
personService.get(personId).then(function(result){
self.person = result.data.person;
});
}
}
}
And then I would have the following view :
<div ng-controller="personController as person">
<label>Name: </label>
<input type="text" ng-disabled="!person.isUpdate" ng-model="person.person.name" />
<button ng-click="person.isUpdate = !person.isUpdate;">Edit</button>
<!-- Edit : the code 'person.isUpdate = !person.isUpdate;' could also be into a controller's function (like the save function) -->
<button ng-click="person.save()" ng-if="person.isUpdate">Save</save>
</div>
I also recommend you to read this : https://github.com/johnpapa/angular-styleguide
Separation of concerns means that you have a well defined structure of your application: the data model in the application is decoupled from the business and presentation logic. It is the base of the MVC pattern, which defines the view, the controller and the model.
This separation makes the code maintainable and easy to test.
Follow this guidance how to architect your Angular application.
The model should:
Include the domain data;
Implement the management of the domain data (query, edit, delete, storing mechanism, REST implementation, http fetching);
Expose an API that makes possibly the model usage in controller or other service;
The model should not:
Provide the details on how the domain data is managed (all REST implementation, http calls should be encapsulated in the model);
Contain logic that transforms the model based on user interaction (it is controller's role);
Contain logic for displaying data to the user (this is the view’s job);
A controller should:
Contain the logic required to initialize the scope;
Contain the logic/behaviors required by the view to present data from the scope;
Contain the logic/behaviors required to update the scope based on user interaction;
A controller should not:
Contain logic that manipulates the DOM (that is the job of the view);
Contain logic that manages the persistence of data (that is the job of the model);
Manipulate data outside of the scope;
A view should:
Contain the logic and markup required to present data to the user
A view should not:
Contain complex logic (this is better placed in a controller);
Contain logic that creates, stores, or manipulates the domain model.
This guidance is taken from this awesome book, which I recommend for any starting AngularJS developer.
Related to your example, in my opinion you should:
Create a controller and a template for editing and adding; Depending on model's isNew property, you can apply editing or adding action;
Create a controller and a template for viewing the model;
Optionally, if you have a collection of models, you can create a new controller and view also.
However it depends on the amount of logic behind the model. If the model is trivial, probably you can implement everything in a single controller and view. But it's rare.

Originzational MVC and Angular keeping from injecting what I don't need

My problem is I have one ng-app. Does that mean I have to do dependency injection for plugins I may not be using on that given view? Example I bring in ngTagsInput does that mean I have to do it even when the view doesn't call for it? That would mean I have to include that js for every view even if it doesn't use ngTagsInput.
I have a very large MVC .NET application and I am trying to figure out what is he best way to handle bringing in external plugins.
I have some code like so in our Main _Layout template:
<html ng-app="ourApp">
<head>
<!-- all of our includes are here -->
</head>
<body>
<directive></directive>
<anotherdirective></anotherdirective>
#RenderBody()
</body>
</html>
RenderBody is where MVC slides in our views from our mvc routing.That view may look like so:
<script src="~/Scripts/HomeAngular.js"></script>
<div ng-controller="HomeCtrl">
<directive3></directive3>
</div>
App JS:
var app = angular.module('ourApp', ['ngTagsInput']);
IS there a way I can get around having to inject ngTagsInput on every view page even if i don't need it?
There are several different ways to handle Dependency Injection (DI) in angular. In this first example, you simply define ngTagsInput before declaring the controller. If ngTagsInput is a service, for example, you'll need to return an object with methods that allow you to access the data inside of that service.
For example:
app.service('ngTagsInput', function($scope) {
var data = {};
return {
getData = function() {
return data;
},
setData = function(val) {
data = val;
}
};
});
app.controller('HomeCtrl', function($scope, ngTagsInput) {
// ...
$scope.tags = ngTagsInput; // whatever you want to do with it
});
However, there's a problem...
In the example above, if you run that code through a minifier, you'll get $scope turned into some variable (a, b, x, etc...) and angular wont know what to do with that.
In this method, we use an array. The last item in your array is your lambda function for the controller, which takes 1 argument for each previous item in the array:
app.controller('HomeCtrl', ['$scope', 'ngTagsInput', function(scp, ngTagIn) {
// ...
}]);
This code can be run through a minifier just fine, since the dependencies are strings in the array and can be renamed to anything you want inside the function's parameters.
DI also works in directives, factories, filters, and services with the same syntax; not just controllers and modules.
app.directive('HomeCtrl', ['$scope', 'ngTagsInput', function(scp, ngTagIn) {
// ...
}]);
Couldn't you break down your application further into smaller modules instead of just one. Then you can inject the smaller modules into the app module and only inject the ngTagsInput dependency on the modules that actually need it. That's how I typically break up my application by function or area.

Critical view on object based application structure in AngularJS

As i tried to learn some Angular in the past weeks, i have adopted a certain application architecture.
Assuming a intermediate large SPA, i create the view and apply a maincontroller:
<html ng-app="app">
<body ng-controller="appCtrl">
<!-- All of the view goes in here -->
</body>
</html>
Now i think about the stuff that my application will have to do. Assuming that this one would have to store some data and working with it, i would create a provider to build a "class".
var data = angular.module('data', []);
data.provider('$dataObject',function(){
this.$get = function($http){
function DataObject(){
var field = "testvalue";
var object = {
// ...
}
}
DataObject.prototype.processData = function(){
// do something
};
return {
Shipment: function(){
return new Shipment();
}
}
}
});
I would then create an instance of that class in my maincontroller, storing all of the necessary data from the view into that object, processing it by calling the class methods.
If the application would need another functionality, e.g a dialog to pick some stuff, i would repeat the steps above and create a class for that dialog.
Now, while i am quite comfortable to do things that way, i still wonder if someone would consider this bad practice, and if so, why is that and what could i do better?
Creating services/providers for your models/data (with APIs to expose the data), and then injecting them into controllers is the "Angular way."
When I design an angular app, I think about the models I need and create services for them. I then (or in parallel) think about the views and design those. I normally create custom directives at this point also. Lastly, each view gets a controller, whose job it is to glue the models/data from the services that the view needs. (Make controllers as thin as possible.)

Ember.js views (unlimited number)

so I need to be able to support an (finitely) unlimited number of views in ember.
Basically, I'm creating a survey app. The survey can have an undefined number of steps, hence an undefined number of views/controllers. The number of steps is got from the server, along with the content and the handlebars template (there are only a finite number of possible content types for a step), and I need some way of being able to extend the survey for the different surveys. So basically one ember app handles the entire thing, I've managed to abstract my code for rendering the content types out enough that it doesn't matter for the ember step: i now just need to make it support an unlimited number of steps
Is it possible to have ember views and controllers, but instead of referencing them by name, reference them by array indice? Like normally you'd go App.TestView = …, and router.get('applicationController').connectOutlet('test'), but could I use App.AllView[0] = Ember.View.extend(); and router.get('applicationController').connectOutlet('test')[0] or something? Then I can have a for loop at the start, which grabs the survey data which contains the content types and number of steps, plug it in a view array, and we're away
There might be other ways to achieve this, so any solutions would be great. Creating a new ember app is not an option, the same app needs to service whatever is passed in as a survey object.
You could create an array of view classes that contains the different steps. You can add new views to the array using the handlebars template name.
App.surveyViews = [];
App.surveyControllers = [];
App.surveyViews['firstStep'] = Em.View.extend({ templateName: 'my-handlebars-template' });
App.surveyControllers['firstStep'] = Em.Controller.extend();
Your route can take in the step from the route and use that to look up the appropriate view class and controller to use for the connectOutlet.
step: Em.Route.extend({
route: '/:step',
connectOutlets: function(router, context) {
router.get('applicationController').connectOutlet({
viewClass: App.surveyViews[context.step],
controller: App.surveyControllers[context.step].create(),
context: {}
});
}
})
You can add any extra logic you need for the context, etc., but that would be the basic idea.

How can I structure a Backbone.View based plugin so that its nested views can be extended individually?

I am designing a generic object browser plugin which functions similar to OS X's Finder in column view. I have divided up the interface in to several nested views, the browser, the columns and the objects.
I will be using this plugin in several scenarios where the browser view, object view and column view may or may not need to be customised. Sometimes the objects will be files and folders for example.
This is OS X's Finder in column view in case you don't know what it looks like.
At the moment I am using RequireJS to pass around the dependencies however in order to simply inherit and extend the ObjectView, I must replace the entire stack.
Is there any better structure where the plugin can be extended but only part of?
BrowserView.js
var BrowserView = Backbone.View.extend({
open: function () {
var collectionView = new CollectionView( {collection: objects} );
}
});
CollectionView.js
var CollectionView = Backbone.View.extend({
render: function () {
this.collection.each( function (object) {
var objectView = new ObjectView( {model: objects} );
objectView.bind('click', this.select, this);
this.container.append( objectView.el );
objectView.render();
this.objectViews.push(objectView);
}, this );
},
});
ObjectView.js
var ObjectView = Backbone.View.extend({
});
I would put these views in the same module.
The purpose of a module - whether you're using RequireJS or just plain old JavaScript modules - is to encapsulate a set of related objects and functions, for a specific purpose. In this case, your purpose is the Finder View.
By keeping all of the related objects in the same file, you'll have more freedom and flexibility for how you make the objects work together.
As a side note, but related to what you're doing, you might be able to get some ideas for how to make this work from the "CompositeView" of my Backbone.Marionette plugin. I've built a hierarchical tree-view of folders and files with it before, and the column view of Finder would be fairly easy to build with it, too.
Note that I'm not suggesting you need to use my plugin. Rather, I think it might be helpful in figuring out how you want to structure your code.
I've got a blog post that talks about it here: http://lostechies.com/derickbailey/2012/04/05/composite-views-tree-structures-tables-and-more/
You can find the code and docs here: https://github.com/derickbailey/backbone.marionette
And the annotated source code for the composite view is here: http://derickbailey.github.com/backbone.marionette/docs/backbone.marionette.html#section-26

Categories

Resources