I'm sure that someone already encountered this problem and posted here, but I just couldn't find it. So sorry for the dupe.
I am making a dashboard where there has to be a chart for every data set from the database.
I tried doing it with sending the data to a directive and draw the chart from within the directive's controller with the data passed to the directive.
This is the partial view
<div class="row">
<div class="col col-md-9">
<input type="text" class="form-control" ng-model="nameFilter" placeholder="Search for...">
</div>
</div>
<div class="row" ng-if="loading">
<div class="col col-md-6 col-md-offset-3">
<img src="../img/loader.gif"/>
</div>
</div>
<div class="row" ng-repeat="app in apps | filter: nameFilter">
<app-card name="app.name" android="app.android" ios="app.ios" date="app.date"></app-card>
</div>
The partial sends data to the directive
var app = angular.module('app');
app.directive('appCard', function() {
return {
restrict: 'E',
scope: {
name: '=name',
android: '=android',
ios: '=ios',
date: '=date'
},
templateUrl: 'http://testsite.nl/dashboard/partials/directives/app-card.html'
};
});
The directives template
<div class="panel panel-info app-card" ng-controller="appCardController">
<div class="panel-heading">
<div class="row">
<h4 class="col col-md-5">{{name}}</h4>
</div>
</div>
<div class="panel-body">
<div class="col col-md-12">
<h3>Android downloads: {{android}}</h3>
<h3>iOS downloads: {{ios}}</h3>
<div id="container" style="min-width: 310px; height:250px; margin: 0 auto;"></div>
</div>
</div>
</div>
And the controller looks like this
app.controller('appCardController', ['$scope', function($scope){
console.log("Downloads: " + $scope.android);
Highcharts.chart('container', {
chart: {
type: 'area'
},
title: {
text: 'Aantal downloads'
},
xAxis: {
allowDecimals: false,
categories: $scope.date
},
yAxis: {
title: { text:'Donwloads' },
floor: 0,
ceiling: getMaxDownloadCount()
},
tooltip: {
pointFormat: '{series.name} <b>{point.y:,.0f}</b>'
},
series: [{
name: 'Android',
data: convertAndroidArrayToInts($scope.android)
},
{
name: 'iOS',
data: convertIosArrayToInts($scope.ios)
}]
});
function convertAndroidArrayToInts(array){
var androidArray = new Array();
for(var i =0; i < array.length; i++){
androidArray.push(parseInt(array[i]));
}
return androidArray
}
function convertIosArrayToInts(array){
var iosArray = new Array();
for(var i =0; i < array.length; i++){
iosArray.push(parseInt(array[i]));
}
console.log(iosArray);
return iosArray
}
}]);
It shows the directive templates with the correct name and array values, the only problem is that there is only one chart drawn, which is the chart beloning to the last item in the list, but it is located in the first template.
How would I be able to make it so every template contains a chart beloning to its data?
I answered a very similar question related to using angular-chart, see my plunker here: https://plnkr.co/edit/8fJ4u0U2fL4lYTioCbG0?p=preview
The question/answer here: angular ng-repeat with directive
The actual issue you face is that the ID is container, and you cannot have duplicate "id" attributes in the DOM, so it will only find the first one.
Assuming the "name" is unique, change the controller so it uses the directives scope.name on Highchart.chart and change the directive template so the <div id="container"> becomes <div id="{{name}}"
After this you should have unique ID's and it will work
Hope that makes sense.
Related
I'm creating a set of widgets with AngularJS 1.5's new components. The problem is, when using the same widget multiple times, they somehow share their controller or scope. I thought one of the things about components was that their scope is completely isolated?
My main html template which hold the widgets:
<widget-list
title="Books"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Movies"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Albums"
class="col-xs-12 col-md-4">
</widget-list>
My widget template:
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
My widget component:
app.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title : '#',
},
controllerAs: '$widget',
controller: function($timeout) {
$widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
});
When running my code, this is what my console looks like:
Title on init: Books
Title on init: Movies
Title on init: Albums
(3) Title after 3 seconds: Albums
Also after rendering, all three widgets display No content in their template. But, when clicking the doSomething() button in either one of the three widgets, only the content of the last widget updates to Something.
What is happening here? Why are my components not 'isolated'?
Looks like you have a global variable called $widget here, try this:
var $widget = this;
instead of
$widget = this;
It creates a mess since the $widget variable holds a reference to the controller that has been recently initialized, in this case to the controller of the third component.
The problem with your code is that you are declaring the $widget on window scope, that's why your controller prints the last value, bacause it was being overridden every time the controller was getting instantiated. Use a var $widget instead and your code will work fine.
The following snippet solves this issue:
angular.module('app', [])
.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title: '#',
},
controllerAs: '$widget',
controller: WidgetListController
});
function WidgetListController($timeout) {
var $widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.min.js"></script>
<widget-list title="Books" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Movies" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Albums" class="col-xs-12 col-md-4">
</widget-list>
<script type="text/ng-template" id="template/widget/widget-list.html">
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
</script>
In my current project I need to create a dynamic form using AngularJS.
I am already building the form following the ideas from this video here.
I can't seem to get the submitted data back to my controller. I only receive undefined in the console log.
Currently the data for the form is resolved in ui-router before the state is loaded, then copied to the controller's data property.
Unlike the video our form requires that questions are broken down into sections.
There is a ng-repeat over each section in the data, then a nested ng-repeat goes over each question. The type is determined and the proper directive for the question/field type is loaded to via ng-switch.
I whipped up a small Plunker to help illustrate as well.
https://plnkr.co/edit/6dCnHiFDEYu03kfX07mr
Finally there are some types I am unsure how to handle, such as address or phone number which will be considered one question type but have multiple fields.
(e.g. [Street] [City] [State] [Zip])
Controller
namespace portal.dashboard.form{
class formCtrl{
formData: portal.domain.interfaces.IHousingRequest;
static $inject = ["formResolve"];
constructor(private formResolve:any) {
this.formData= this.loadHousingRequestFormData;
}
public submit(isValid,data) {
if (isValid) {
console.log(data);
}
}
}
angular
.module("portal")
.controller("formCtrl", formCtrl);
}
Directive for input type text
namespace portal.directives {
function inputText(): ng.IDirective {
return {
scope: {
model: '='
},
controller: function ($scope: ng.IScope) {
var directiveScope = $scope.$parent.$parent;
},
controllerAs:'vm',
templateUrl: 'form/templates/input-text.html'
}
}
angular
.module("portal")
.directive("inputText", inputText);
}
input type html
<input type="text"
ng-model="model"/>
HTML
<form name="form" ng-submit="vm.submit(form.$valid, data)" novalidate>
<!-- Section repeat -->
<div ng-repeat="section in vm.formData.sections track by $index">
<section>
<div>
<h4>
{{section.name}}<br />
<small ng-show="section.description">{{section.description}}</small>
</h4>
</div>
<section>
<!-- Section questions repeat -->
<div ng-form="formFields" ng-repeat="field in section.fields track by $index">
<label>
{{field.name}}<br />
<small>{{field.description}}</small>
</label>
<!-- input field switch -->
<div ng-switch="field.type">
<div ng-switch-when="Text">
<input-text model="data.answer[$index]">
</input-text>
</div>
<div ng-switch-when="Email">
<input-email model="data.answer[$index]">
</input-email>
</div>
</div>
</div>
</section>
</section>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
You have to init $scope.data = {}; before using it, also use correct sectionIndex and fieldIndex to populate the answer:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {};
$scope.sections = [{
name: 'User Info',
description: 'I\'m a description.',
fields: [{
label: "Name",
type: "text"
}, {
label: "Email",
type: "email"
}]
}, {
name: 'Pet Info',
description: '',
fields: [{
label: "Pet Breed",
type: "text"
}]
}];
$scope.submit = function(isValid, data) {
console.log('submit fired');
if (isValid) {
console.log(data);
}
}
});
app.directive('inputText', function() {
return {
scope: {
model: '='
},
controller: function($scope) {
var directiveScope = $scope.$parent.$parent;
},
controllerAs: 'vm',
template: '<input type="text" ng-model="model"/>'
}
});
app.directive('inputEmail', function() {
return {
scope: {
model: '='
},
controller: function($scope) {
var directiveScope = $scope.$parent.$parent;
},
controllerAs: 'vm',
template: '<input type="email" ng-model="model"/>'
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<body ng-app="plunker" ng-controller="MainCtrl">
<form name="form" ng-submit="submit(form.$valid, data)" novalidate>
<!-- Section repeat -->
<div ng-repeat="(sectionIndex, section) in sections track by $index">
<section>
<div>
<h4>
{{section.name}}<br />
<small ng-show="section.description">{{section.description}}</small>
</h4>
</div>
<section>
<!-- Section questions repeat -->
<div ng-form="formFields" ng-repeat="(fieldIndex, field) in section.fields track by $index">
<label>
{{field.label}}<br />
</label>
<!-- input field switch -->
<div ng-switch="field.type">
<div ng-switch-when="text">
<input-text model="data.answer[sectionIndex][fieldIndex]">
</input-text>
</div>
<div ng-switch-when="email">
<input-email model="data.answer[sectionIndex][fieldIndex]">
</input-email>
</div>
</div>
</div>
</section>
</section>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
</body>
Also I'm not sure why do you need this var directiveScope = $scope.$parent.$parent; in your directive's controller, do you really need this?
Error: https://docs.angularjs.org/error/ng/areq?p0=ProjectsController&p1=not%20a%20function,%20got%20undefined
I mimicked a ng-repeat that I did earlier, the only difference being I didn't split up the directive, controller, and initializing the app into 3 different javascript files. Also, are you allowed to add multiple ng-apps to the <body> tag? If no, then that may be my problem.
HTML:
<div class="projects">
<h2>Projects</h2>
<div class="row">
<div class="col-lg-12">
<div class="row" ng-controller="ProjectsController">
<div class="project-boxes" ng-repeat="project in projects">
<project-info info="project"></project-info>
</div>
</div>
</div>
</div>
</div>
app.js:
var projectApp = angular.module("projectApp", ['ngAnimate'])
.controller('ProjectsController', ['$scope', function($scope) {
$scope.projects = [
{
link: '#',
img: 'img/box.jpeg',
description: 'Project 1'
}];
}])
.directive('projectsInfo', function() {
return {
restrict: 'E',
scope: {
info: '='
},
templateUrl: 'components/projects/projects-template/projectsInfo.html'
};
});
I have set up a fiddle to explain my question well. I would like to display the names from the $scope.gem inside ng-repeat [only one name for each ng-repeat and don't loop all] of $scope.knobItems without extending the knobItems scope. I want this to be made possible by maintaining the exact structure of controller as it is now. I am new to angular. I just wanna know if this is possible in angular and if is a good practice.
view
<div ng-app="myapp">
<div ng-controller="Mycont">
<div ng-repeat="knobs in knobItems">
<div ng-repeat="(key, value) in knobItems.nums">{{value.knobTitle}} : {{value.knobColor}}
<div ng-bind="gem[0].name"></div>
</div>
</div>
</div>
</div>
controller
var ngMod = angular.module("myapp", []);
ngMod.controller("Mycont", function ($scope) {
$scope.knobItems = {};
$scope.knobItems.nums = [{
knobTitle: "Company Profile",
knobColor: "#f46607"
}, {
knobTitle: "Deals left This Month",
knobColor: "#ffcc00"
}, {
knobTitle: "Pricelist",
knobColor: "#f40787"
}, {
knobTitle: "Pictures",
knobColor: "#a1b80a"
}, {
knobTitle: "Videos",
knobColor: "#14b9d6"
}];
$scope.gem = [{
name: "Thomas"
}, {
name: "Sebastian"
}, {
name: "June"
}, {
name: "Yuvan"
}];
});
intended output
Easy fix: fiddle
<div ng-app="myapp">
<div ng-controller="Mycont">
<div ng-repeat="knobs in knobItems">
<div ng-repeat="(key, value) in knobItems.nums">{{value.knobTitle}} : {{value.knobColor}}
<div ng-bind="gem[$index].name"></div>
</div>
</div>
</div>
</div>
The output in you fiddle is exactly the same without the first ng-repeat: http://jsfiddle.net/2nrbrfxL/
Going by you description rather than you code:
<div ng-app="myapp">
<div ng-controller="Mycont">
<div ng-repeat="knobs in knobItems">
<div ng-repeat="(key, value) in knobs">{{value.knobTitle}} : {{value.knobColor}}
<div ng-repeat="gemItem in gem">{{gemItem.name}}</div>
</div>
</div>
</div>
</div>
http://jsfiddle.net/p2fuq2du/
<div ng-app="myapp">
<div ng-controller="Mycont">
<div ng-repeat="(key, value) in knobItems.nums">{{value.knobTitle}} : {{value.knobColor}}
<div ng-bind="gem[key].name"></div>
</div>
</div>
</div>
I have a multidimensional array from an API. Is it possible to programatically loop through the array?
{
success: true,
categories: [{
cat_id: "2",
name: "This is category One",
description: null,
photo_url: "/img/test.png",
order: "1",
items: [{
item_id: "1",
title: "Desk",
item_url: "/690928460",
photo_url: "/desk.png",
}, {
item_id: "2",
title: "Chair",
item_url: "/18882823",
photo_url: "/chair.png",
},
}]
}]
}
My controller looks like this:
myApp.controller('items', function($scope, $http, $location, Data) {
var apiUrl = '/api/items';
$http.get(apiUrl).
success(function(data) {
$scope.data = Data;
$scope.allData = data;
});
$scope.changeView = function(view) {
$location.path(view);
}
});
Angular index file just has: <div ng-view=""></div>
View file
<div class="scrollable categories-container animated-fast slideInUp">
<div class="container categories">
<div class="row" ng-repeat="categories in allData">
<div class="col-xs-6" ng-repeat="category in categories">
<div class="items">
<div class="title">
{{ category.name }}
</div>
</div>
</div>
</div>
</div>
</div>
I can loop through the category names fine, but when trying to return items for EACH category I don't understand the logic behind it...
I would suggest some simple nested for loops, as for each gives rise to more complexity.
As I'm not sure what you want to do with the data let's just create an array of all item names and one of all category names:
Within your success function:
var items = [], categories = []
for(var i = 0; i < data.categories.length;i++){
categories.push(data.categories[i].name);
for(var j = 0; j < data.categories[i].items.length;j++){
items.push(data.categories[i].items[j].name);
}
}
console.log(categories);
console.log(items);
EDIT:
Completely missed your html code somehow, here is my solution:
<div class="scrollable categories-container animated-fast slideInUp">
<div class="container categories">
<div class="col-xs-6" ng-repeat="category in allData.categories">
<div class="items">
<div class="title">
{{ category.name }}
</div>
</div>
</div>
</div>
</div>
EDIT 2:
As to your comment:
If you want to select the secondary view's contents(ie the items) based on the selection of a category I would suggest a ng-click attribute. A directive could be used but isn't necessary:
<div class="scrollable categories-container animated-fast slideInUp">
<div class="container categories">
<div class="col-xs-6" ng-repeat="category in allData.categories">
<div class="title" ng_click="selected_category = category">
{{ category.name }}
</div>
</div>
<div class="col-xs-6" ng-repeat="item in selected_category.items">
<div class="title">
{{ item.name }}
</div>
</div>
</div>
</div>
So when your categories data is loaded the first ng-repeat is populated with the categories. Each div with class title will have a function called on click which will make the selected_category object equal the selected category.
This will then cause the second view to be populated with all the items in the selected category by Angular's two way bind.