Angular GroupBy using without ng-repeat - javascript

In AngularJS there is the groupBy method for ng-repeats. I'm using that to make a list of times a product with the same title is in the array I'm ng-repeating, and calculate the length and total price of it like this:
<li ng-repeat="(key, value) in products | groupBy: 'title'">
<span>{{value.length}} items</span>
<strong>{{ value[0].title }}</strong>
<tt>{{ (value[0].price*value.length) | currency: '$' }}</tt>
</li>
This works fine, but I wanna re-use that same calculation in the Controller. So I have an object/array like:
{
{
length: 10,
title: 'test 1',
price_total: 200.99,
},
{
length: 3,
title: 'test 2',
price_total: 3.99,
},
}
But I can't figure out how to use the same GroupBy kinda thing.

You can inject AnguarJS filters like this:
angular.module('myApp')
.controller('MyCtrl', function($filter, $scope) {
$scope.myCollection = [{
title: 'Wut',
text: 'Wat'
}, {
title: 'Que',
text: 'paso'
}]
$scope.grouped = $filter('groupBy')($scope.myCollection, 'title');
})
in order to get the same functionality that you would from a directive. Documentation here

Related

Implementing an accordion in AngularJS and better understanding $scope

I am editing an already existing Angular webpage. In the app.js file, there are multiple controllers for each page. Here's their template:
JS:
app.controller("myCtrl", function($scope) {
$scope.useSettings({
Header: "header",
Title: "title",
mainImg: "main.png",
fields: [{
fieldText: "text",
},{
fieldImg: "pic.jpg",
},{
fieldAccordion:
//
},{
]
});
});
My goal is to have fieldAccordion work as an accordion for the data that I input there, just how all my fields work.
This is my Unordered List data type in the html file:
<div class="basic-page__field__undordered" ng-
if="field.fieldUnorderedList">
<ul class="field-list field-list--unordered">
<li ng-repeat="listItem in field.fieldUnorderedList">{{ listItem }}
</li>
</ul>
</div>
I can simply use it in the .JS file like this:
fieldUnorderedList: [
"item1",
"item2",
]
So I would like to find a similar way to use data for an accordion type.
Here is how I wrote it in the .html file:
<div class="basic-page__field__accordion" ng-if="field.fieldAccordion">
<ul class="field-list field-list--accordion">
<li ng-repeat="item in items" ng-click="accordion.current = item.name">
{{item.name}}
<ul ng-show="accordion.current == item.name">
<li ng-repeat="sub in item.sub">{{sub.name}}</li>
</ul>
</li>
</ul>
</div>
Now the issue that I'm having is getting it to work as simple as using
"fieldAccordion:" in my controller.
I have done some research and what I don't understand is this:
All my controllers have the same template with $scope.useSettings, followed by all the data such as fieldText, fieldImg, etc.
How would I add another $scope that deals with the accordion data type which I can use in the exact place that I want it to be?
Something like this:
$scope.accordion = {
current: null
};
$scope.items = [{
name: 'List 1',
sub: [{
name: 'Sub 1.1'
},{
name: 'Sub 1.2'
}]
},{
name: 'List 2',
sub: [{
name: 'Sub 2.1'
}]
},{
name: 'List 3',
sub: [{
name: 'Sub 3.1'
},{
name: 'Sub 3.2'
},{
name: 'Sub 3.3'
}]
}];
}]);
This is one of my biggest obstacles in getting to understand AngularJS. Since I am working on an already existing webpage, I don't know how to add multiple $scopes for one controllers and where to use them. Any help is appreciated.

How can I use ng-repeat to iterate through arrays associated using dynamic keys

I am trying to use ng-repeat to iterate through an array of objects and use each objects ID to look up the data binded to a checklist model.
I have the following javascript object in a project I'm working on:
{
diagnosis: {
mainfractures: [
{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
},
{
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
},
{
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
},
{
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
},
{
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}
]
}}
Here is what I have for my checklist model. As the user selects a checkbox, a value is binded to the array associated with that fracture.
$scope.checklists = {
"diagnosis": {
metacarpal: [],
proximal_phalanx: [],
middle_phalanx: [],
distal_phalanx: [],
scaphoid_fracture: []
}
}
Checklist Image Example
Once a users makes a selection similar to the image above the the checklist model for metacarpal should look like this: metacarpal: ["head"]
What I'm trying to do is list each of the users selection in bulletpoint via fracture.id. I'm trying to accomplish it with this piece of code but it's only listed the fracture title so far. is it a problem with trying to interpolate fracture.id into ng-repeat?
<div ng-repeat="fracture in diagnosis.mainfractures">
<div > <!--ng-if="checklists['diagnosis'][fracture.id] > 0"-->
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ capitalize(selection) }}
</li>
</ul>
</div>
</div>
</div>
Based on your supplied code, I'd have to say your issue is actually due to JS syntax errors. You're missing commas after each object item and there is a random double quote here scaphoid_fracture"[].
$scope.checklists = {
"diagnosis": {
metacarpal: []
proximal_phalanx: []
middle_phalanx: []
distal_phalanx: []
scaphoid_fracture"[]
}
}
Here is a fully working jsfiddle
Adjusted the code a bit:
capitalize ->
uppercase(https://docs.angularjs.org/api/ng/filter/uppercase)
some syntax errors
But seems that access by dynamic object key inside ng-repeat works pretty correct
https://jsbin.com/rodecosoyo/1/edit?html,js,output
Angular Application
var app = angular.module('arrayid', []);
app.controller('arrayContainerCtrl', function($scope) {
$scope.diagnosis = {
mainfractures: [{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
}, {
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
}, {
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
}, {
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
}, {
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}]
};
$scope.checklists = {
"diagnosis": {
metacarpal: ['1', '2'],
proximal_phalanx: ['2', '3'],
middle_phalanx: ['3'],
distal_phalanx: ['4'],
scaphoid_fracture: ['5']
}
};
});
Markup:
<body ng-app='arrayid' ng-controller='arrayContainerCtrl'>
<div ng-repeat="fracture in diagnosis.mainfractures">
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ selection | uppercase }}
</li>
</ul>
</div>
</div>
</body>

How to watch for a model's deep changes, from templates?

I have a model on my scope, which is an object of objects. I have seen this, but I want to do this from the template as I have a filter defined on it.
var App = angular.module('app', []);
App.controller('myCtrl', function($scope) {
$scope.items = {
{ name: 'Cricket bat', cost: '2500', quantity: 0},
{ name: 'Football', cost: '1100', quantity: 0}
};
$scope.cartItems = {}; // This holds the items. I want quantity of each item separately so it's not an array.
I have defined a filter getPrice which calculates the price for the items in users cart.
And I have in the template:
{{ cartItems | getPrice }}
Is it possible to have the template update after any of the nested object value (ie. quantity of one of the items from the cart) changes? If yes, how?
Move the quantity field to the cartItem object
$scope.items = {
{ name: 'Cricket bat', cost: '2500'},
{ name: 'Football', cost: '1100'}
};
$scope.cartItems = {
{ name: 'Cricket bat', quantity: 3},
{ name: 'Football', quantity: 6}
};
Then update your getprice filter based on the above json. This should take care of your cost in the cart getting updated when a price of an item changes.
Seems like your strategy is not good overall.
An "object of objects" as you mean it { {id:1}, {id:2} } is just not valid javascript, and will generate an error. Choose an array of objects [ {id:1}, {id:2} ] or a true object { 1: {id:1}, 2: {id:2} }
#Blackhole is right, you don't need to watch anything, interpolation is part of angular.js core and does all that tricky watch stuff for you. Defining a cartItems variable, and interpoling it (with brackets {}) in a template, filtering it or not, will keep the interpolated value in sync with the variable value without you doing anything more. It's even two-ways bound (if you change model, variable will also change). You can verify the sync by removing temporiraly your filter : try to put {cartItems} in your template and update your cart, you'll see the template updated.
This model format would probably be more appropriate
$scope.items = [
{ name: 'Cricket bat', cost: '2500' },
{ name: 'Football', cost: '1100' }
];
$scope.cartItems = [
{ item: { name: 'Cricket bat', cost: '2500' }, quantity: 3 },
];

Angular.js: How do I use ng-bind to display concat. elements of an array as a string?

I am new to Angular and have a basic question about ng-bind that I couldn't find in the documentation. My scenario is based the shopping cart app in the O'Reily Angular.js book and I cannot seem to get ng-bind to work.
Desired output: I need to modify my controller function so I can show my updated $scope.items array elements in a 'Grand Total' span.
Here is the function:
function CartController($scope) {
$scope.items = [
{title: 'Software', quantity: 1, price: 1399.95},
{title: 'Data Package (1TB)', quantity: 1, price: 719.95},
{title: 'Consulting (per hr.)', quantity: 1, price: 75.00}
];
$scope.remove = function(index) {
$scope.items.splice(index, 1);
},
$scope.reset = function(index) {
$scope.items = [
{title: 'Software', quantity: 0, price: 1399.95},
{title: 'Data Package (1TB)', quantity: 0, price: 719.95},
{title: 'Consulting (per hr.)', quantity: 0, price: 75.00}
];
};
}
I would recommend making a grandTotal function on your $scope and then binding that, like this:
http://jsfiddle.net/XMTQC/
HTML
<div ng-app ng-controller="CartController">
Grand Total: <span>{{grandTotal()}}</span>
<br/>
Grand Total: <span ng-bind="grandTotal()"></span>
<br/>
</div>
JavaScript
$scope.grandTotal = function () {
return $scope.items.reduce(function (p, c) {
return p.price || p + c.price;
});
};
You can also use interpolation (instead of ngBind) as indicated in the first span.

Looping through deep objects in ng-repeat

I'm in angular and i have a object like this.
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'I'm a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if I'm going to have children'}
]}
]}
]}
]
}];
I wan't to loop through all of these so i have something like this.
    • Something
       • Hello World
       • Hello Overflow
       • John Doe
          • Amazing Title
          • Google it
          • I'm a child
              • Another
              • He's my brother
              • She's my mother
                  • You never know if I'm going to have children
The problem is I wouldn't know how deep this object will go or what's in it. so I wouldn't be able to do it manually. I have done a basic loop with ng-repeat in the fiddle provided at the bottom, but i can't figure out how I can automatically loop through these and create nested <ul>'s and <li>'s.
What would be the best way to accomplish this?
Demo: http://jsfiddle.net/XtgLM/
You don't need to make a custom directive, what you want is to use an inline template that calls it's self.
I forked your fiddle.
http://jsfiddle.net/MasterMorality/E99Gh/2/
basically it looks like this:
<script type='text/ng-template' id="item.html">
...
<div ng-repeat="x in x.childrens" ng-include="'item.html'"></div>
</script>
...
<div ng-repeat="x in things" ng-include="'item.html'"></div>
I should note that you are not actually overwriting x, since angular creates a new scope for each repeated item.
Here you go:
html
<div ng-app="app" ng-controller="test">
<ul>
<li nested-item ng-repeat="item in items">{{item.title}}</li>
</ul>
</div>
JavaScript
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'Im a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if im going to have children'}
]}
]}
]}
]
}];
var app = angular.module('app', []);
app.controller('test', function( $scope ) {
$scope.items = items;
});
app.directive('nestedItem', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope, element){
console.log(element);
if (scope.item.children){
var html = $compile('<ul><li nested-item ng-repeat="item in item.children">{{item.title}}</li></ul>')(scope);
element.append(html);
}
}
};
}]);
I forked your fiddle:
http://jsfiddle.net/c4Kp8/
Actually I must confess that I like Master Morality's approach but you can also go with a custom directive. The key thing to know if you go that route is that you need to intercept on the item level to manually check if the current item has children, and if so, $compile the directive for the node yourself.
UPDATE
However, there is one thing that should bother us in the above code. The duplication of html code (inlined in the directive) is a code smell. If you like, you can get really funky and fix this by introducing a generic template-code directive which doesn't do anything else but providing the code of the node where it is applied on as a template for other directives.
So then our solution would look like this:
html
<div ng-app="app" ng-controller="test">
<ul template-code>
<li nested-item ng-repeat="item in items">{{item.title}}</li>
</ul>
</div>
JavaScript
var items = [{
title: 'Something',
children: [
{ title: 'Hello World' },
{ title: 'Hello Overflow' },
{ title: 'John Doe', children: [
{ title: 'Amazing title' },
{ title: 'Google it' },
{ title: 'Im a child', children: [
{ title: 'Another ' },
{ title: 'He\'s my brother' },
{ title: 'She\'s my mother.', children: [
{title: 'You never know if im going to have children'}
]}
]}
]}
]
}];
var app = angular.module('app', []);
app.controller('test', function( $scope ) {
$scope.items = items;
});
app.directive('templateCode', function(){
return {
restrict: 'A',
controller: function(){},
compile: function(element){
element.removeAttr('template-code');
//ATTENTION: We need to trim() here. Otherwise AngularJS raises an exception
//later when we want to use the templateCode in a $compile function.
//Be aware that we assume a modern browser
//that already ships with a trim function.
//It's easy to secure that with a polyfill.
var templateCode = element.parent().html().trim();
return function(scope, iElement, iAttrs, controller){
controller.templateCode = templateCode;
}
}
}
});
app.directive('nestedItem', ['$compile', function($compile){
return {
restrict: 'A',
require: '^templateCode',
link: function(scope, element, iAttr, controller){
if (scope.item.children){
scope.items = scope.item.children;
var html = $compile(controller.templateCode)(scope);
element.append(html);
}
}
};
}]);
Plunker: http://jsfiddle.net/2rQWf/
You're probably going to need to create your own directive passing in the object to iterate over. Put a watch on the object passed in and when that fires run some recursive function that appends elements to the element that the directive is on.
You can get at the DOM element from the element parameter on the directive link function. You can obviously append DOM elements using that same element parameter.

Categories

Resources