Using two controllers within the same form, but code fails (angularjs) - angularjs

Still new to AngularJS and I'm trying to combine two ng-controllers together.
Before I was trying to do this, the initial selected drop down option was correctly chosen. Right now as it stands, the drop down option I designed to be automatically selected is not selected initially.
The reason it's not, is because I am trying to play around with two ng-controllers. The old one works correctly if used by itself. This is the one for the initial drop down option to be correctly selected. Then, there is the second (and newer) ng-controller which is used to "print out" reviews. This controller basically pushes printed reviews onto the web page.
I tried adding that controller to the old one. I tried using that controller separately and add two ng-controllers in my form. I tried placing the drop down menu in a separate div and even an li afterwards and tried adding its own controller in there. So far... no luck.
Anything else I can try?
This is my plunker - it won't print out the text, though it does the rest:
http://embed.plnkr.co/tUTn4L02B57gV1V7psfk/preview
These are the specific parts:
HTML:
<blockquote ng-repeat="review in product.reviews">
<b>Stars: {{review.stars}}</b>
<br/>
{{review.body}}
<br/>
<cite>by: {{review.author}}</cite>
</blockquote>
<form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewCtrl.addReview(product)">
<blockquote>
<b> Stars: {{reviewCtrl.review.stars}}</b>
<br/>
<b> Review: {{reviewCtrl.review.body}}</b>
<br/>
<cite>by: {{reviewCtrl.review.author}}</cite>
</blockquote>
<my-stars controller="starsController" model="reviewCtrl.review.stars" style="margin-bottom:50px;margin-left:36px;width:350px;"></my-stars>
JS:
app.controller('starsController', ['$scope', function($scope) {
$scope.review = {};
$scope.review.stars = '5 stars';
}])
app.directive('myStars', function() {
return {
restrict: 'E',
scope: { model: '=' },
template: '<select ng-model="model" ng-options="option.name as option.value group by option.type for option in options"></select>',
controller: function($scope) {
$scope.options = [
{ name: '1 star', value: '1 star', type:"Rate the product" },
{ name: '2 stars', value: '2 stars', type:"Rate the product" },
{ name: '3 stars', value: '3 stars', type:"Rate the product" },
{ name: '4 stars', value: '4 stars', type:"Rate the product" },
{ name: '5 stars', value: '5 stars', type:"Rate the product" }
];
}
};
/*
this.review = {};
this.addReview = function(product) {
product.reviews.push(this.review);
};
*/
});
app.controller("ReviewController", function() {
this.review = {};
this.addReview = function(product) {
product.reviews.push(this.review);
};
});

Firstly, as #WillIAM already wrote, it's as minimum strange to assign controller to directive's element whereas you already declared controller in directive itself.
Also, I don't see any code where you selecting current product for reviewing. If you want to have default 5 stars mark then create blank review with this property filled.
Fixed plunker

scope: { model: '=' },
By adding this line to the myStars directive you're giving it an isolated scope. This means that no data will be shared between the controller's scope and the directive's scope. If you want to pass in $scope.review.stars you'll need to pass it through the same way you passed in model
Giving your directive it's own controller like this:
<my-stars controller="starsController" model="reviewCtrl.review.stars" style="margin-bottom:50px;margin-left:36px;width:350px;"></my-stars>
Is kind of strange and defeats the purpose of the directive's controller. I would put everything that's in starsController into the directive's controller like so:
controller: function($scope) {
$scope.model = $scope.model || '5 stars'; //default to 5 stars
$scope.options = [
{ name: '1 star', value: '1 star', type:"Rate the product" },
{ name: '2 stars', value: '2 stars', type:"Rate the product" },
{ name: '3 stars', value: '3 stars', type:"Rate the product" },
{ name: '4 stars', value: '4 stars', type:"Rate the product" },
{ name: '5 stars', value: '5 stars', type:"Rate the product" }
];
}
That should fix your issue.

Related

How to get all checked checkboxes value by ng-model in Angular?

here is the jsfiddle.
HTML:
<div ng-app="app">
<div ng-controller="ctrl">
<div ng-repeat="item in list">
<div mycb group="{{item.group}}" title="{{item.title}}" is-checked="item.isChecked" value="{{item.value}}" update="callMe()"></div>
</div>
<p>{{result}}</p>
</div>
</div>
JS:
angular.module("app",[])
.controller("ctrl", ["$scope", function($scope){
$scope.list = [
{ group: "pet", title: "dog", isChecked: true, value: "dog" },
{ group: "pet", title: "cat", isChecked: true, value: "cat" },
{ group: "pet", title: "bird", isChecked: true, value: "bird" },
{ group: "pet", title: "snake", isChecked: true, value: "snake" },
{ group: "pet", title: "boy", isChecked: true, value: "boy" },
{ group: "pet", title: "cup", isChecked: true, value: "cup" }
];
$scope.callMe = function(){
var collection = [];
for(var i=0;i<$scope.list.length;i++){
var isChecked = $scope.list[i].isChecked;
if(isChecked){
collection.push($scope.list[i].value);
}
}
$scope.result = collection.join(" ");
}
}])
.directive("mycb", function(){
return{
restrict: "A",
scope: {
title: "#",
isChecked: "=",
group: "#",
value: "#",
update: "&"
},
template: "<input type='checkbox' ng-model='isChecked' name='{{group}}' value='value' ng-change='update()'>{{title}}"
};
})
I created a group of checkbox and it will be updated when each of them is clicked.
By default, all checkboxes are checked. When I click the first one, it will be turned to status unchecked. The value of other checked boxes will show up.
For example:
dog,cat,bird,snake,boy,cup
When I click dog, the checkbox of dog will be turned to unchecked and "cat,bird,snake,boy,cup" will show up. Actually, it not happened like that. It shows "dog,cat,bird,snake,boy,cup".
Please check it out and give me a hand. Many thanks!
You can use an arrray to keep track of the boxes that are checked.
$scope.selectedCheckboxes = [];
$scope.callMe=function(item){
var idx = $scope.selectedCheckboxes.indexOf(item);
// is currently selected
if (idx > -1) {
$scope.selectedCheckboxes.splice(idx, 1);
}
// is newly selected
else {
$scope.selectedCheckboxes.push(item);
}
};
And in html pass item.value to callMe function. You wil have all the value that are checked in $scope.selectedCheckboxes
<div ng-repeat="item in list">
<div mycb group="{{item.group}}" title="{{item.title}}" is-checked="item.isChecked" value="{{item.value}}" update="callMe(item.value)"></div>
</div>
HTML
<div ng-repeat="item in list">
<div mycb group="{{item.group}}" title="{{item.title}}" is-checked="item.isChecked" value="{{item.value}}" ng-change="callMe()"></div>
</div>
Use ng-change event. Call Me function called when the user clicked on the checkbox. you can easily track all the checked checkbox in the controller.
Let me know if you need help more. Thanks.

Angular tree directive with only one directive - too much recursion?

I am trying to avoid writing the compile and/or link functions. I only want to use the controller function. Why am I getting "too much recursion"?
The data:
$scope.myTree = {
name: "Root",
id: 1,
items: [
{
name: "Arts",
id: 12,
items: [
{ name: "Sculpture", id: 220 },
{ name: "Painting", id: 221 },
{ name: "Music", id: 222 }
]
},
{
name: "Science",
id: 45,
items: [
{ name: "Biology", id: 345 },
{ name: "Chemistry", id: 346 },
{ name: "Physics", id: 347}
]
}
]
};
The markup:
<tree data="myTree" labelfield="name" valuefield="id" childrenfield="items">
<div>
This is the custom node content.
</div>
The directive:
angular.module("app").directive("tree", function ($compile) {
return {
restrict: "E",
replace: true,
transclude: true,
scope: {
labelfield: "#",
valuefield: "#",
childrenfield: "#",
data: "="
},
controller: function ($scope) {
$scope.children = []; // remember - these are NOT the model's children!!!
if ($scope.data[$scope.childrenfield] !== undefined) {
for (var i = 0; i < $scope.data[$scope.childrenfield].length; i++) {
$scope.children.push({
label: $scope.data[$scope.childrenfield][i][$scope.labelfield],
value: $scope.data[$scope.childrenfield][i][$scope.valuefield]
});
}
}
},
template: "<ul><li ng-transclude></li>" +
"<li ng-repeat='child in children'> {{child.label}}" +
"<tree labelfield='labelfield' valuefield='valuefield' childrenfield='childrenfield'></tree>" +
"</li>" +
"</ul>"
};
});
If I remove the tag in the template, it will show only the first level, otherwise, I'll get infinite recursion.
What is missing? What shouldn't be there?
It appears that while you can't include the same directive inside itself, you can include another directive that includes the first one.
angular ui tree appears to do this with the TreeNode and TreeNodes directives.
[This is a late answer, but I think some people will find this useful.]
Nested directives will cause that "too much recursion" error. You may use RecursionHelper from this post.
After adding RecursionHelper service to your angular module, you just need to compile your directive element using RecursionHelper.compile function.
compile: function(element) {
// Use the compile function from the RecursionHelper,
// And return the linking function(s) which it returns
return RecursionHelper.compile(element);
}

Angular UI Bootstrap's Radio Button inside a directive with ng-repeat does not show default value properly

Essentially, I have a directive that is used as a 3 way filter using a radio button. Unfortunately, it needs to have a default state and that default state has to actually be shown in the UI, the problem is that although the model is updated properly, the UI is not. Here is a plunkr that demonstrates the issue:
http://plnkr.co/edit/8pljDFyRfInI4Q0qSTmL?p=preview
The directive is used the following way:
<filter model="model"/>
Where the model is defined as this.model = { value: {} } in the controller
Here is an updated code.
At first I want to say what ng-model directive works only with input elements,
and also I changed the isActive: 'Yes/No' to true/false
// Code goes here
var filters = angular.module('filters', ['ui.bootstrap']);
filters.controller('FilterCtrl', function() {
this.model = { value: {} };
});
filters.directive('filter', function () {
return {
restrict: 'E',
scope: {
model: '='
},
template: '<div class="btn-group">' +
'<label ng-repeat="choice in choices" class="btn btn-{{ choice.buttonClass }}" ng-class="{active: choice.value.isActive}"' +
'btn-radio="{{ choice.value }}"><i class="fa {{ choice.icon }}"></i> {{ choice.name }}</label>' +
'</div>',
link: function (scope, element, attrs) {
scope.choices = [
{ value: { isActive: true }, name: 'Active', buttonClass: 'default', icon: 'fa-circle' },
{ value: null, name: 'Both', buttonClass: 'primary', icon: 'fa-arrows-h' },
{ value: { isActive: false}, name: 'Inactive', buttonClass: 'danger', icon: 'fa-circle-o' },
];
scope.model.value = _.first(_.filter(scope.choices, { value: { isActive: 'Yes' } })).value;
}
}
});

Angularjs directive not redrawing

I am trying to get a recursive directive working in angular. After quite a bit of time on stackoverflow and digging through the angular documentation I have got most of it working. I'm having a hard time getting the actions working though. The object is getting updated as I would expect, but the directive doesn't seem to be redrawn accordingly.
Here is the directive:
.directive('formgenerator', ['$compile', function ($compile) {
return {
restrict: 'E',
terminal: true,
scope: { val: '=', index: '=' },
replace: true,
link: function (scope, element, attrs) {
var template = '<div data-ng-if="val">';
template += !scope.val.type ? ''
: scope.val.type === 'text' ? '<label>{{val.label}}</label><input type="text" data-ng-model="val.value"></input><button ng-click="deleteMe(index)">delete</button>'
: scope.val.type === 'select' ? '<label>{{val.label}}</label><select data-ng-model="val.value" data-ng-options="v.name for v in val.values track by v.id"></select><button ng-click="deleteMe(index)">delete</button>'
: scope.val.type === 'multiselect' ? '<label>{{val.label}}</label><select data-ng-model="val.value" multiple="multiple" data-ng-options="v.name for v in val.values track by v.id"></select><button ng-click="deleteMe(index)">delete</button>'
: '';
template += '</div>';
if (angular.isArray(scope.val.inputs)) {
template += '<ul class="indent"><li ng-repeat="input in val.inputs track by $index"><formgenerator val="input" index="$index"></formgenerator></li></ul>';
}
scope.deleteMe = function (index) {
scope.$parent.val.inputs.splice(index, 1);
//var inpts = scope.$parent.val.inputs;
//inpts.splice(index, 1);
//scope.$parent.val.inputs = inpts;
//scope.$parent.$parent.val.inputs.splice(index, 1);
//scope.$parent.$parent.$parent.val.inputs[scope.$parent.this.index].inputs.splice(scope.$parent.this.index, 1);
};
var newElement = angular.element(template);
$compile(newElement)(scope);
element.replaceWith(newElement);
}
};
}]);
Here is the object the controller is passing into the directive:
form = {
inputs:
[
{
type: 'text',
value: 'textValue',
label: 'Text value',
defaultValue: 'defaultTextValue'
},
{
inputs:
[
{
type: 'text',
value: 'textValue1',
label: 'Text value1',
defaultValue: 'defaultTextValue1'
},
{
type: 'select',
value: 'textValue2',
values: [{ name: '1st', id: 1 }, { name: '2nd', id: 2 }],
label: 'Text value2',
defaultValue: 'defaultTextValue2'
},
{
type: 'multiselect',
value: 'textValue3',
values: [{ name: '1st', id: 1 }, { name: '2nd', id: 2 }],
label: 'Text value3',
defaultValue: 'defaultTextValue3'
}
]
}
]
};
Here is the jsFiddle: http://jsfiddle.net/5YCe7/1/
Basically, if I hit the delete button next to Text value2, I would expect the single select to 'disappear' and the multiselect to 'move up'. What seems to be happening though is that the values of the multiselect move in place of the values for the select.
Any help on this would be greatly appreciated.
There are a few errors here:
When you press deleteMe, you need to again compile and replace the element with the new element. link won't automatically be called again.
I don't think your index is being assigned correctly
I would recommend looking at a few links before making recursive directives:
Is it possible to make a Tree View with Angular?
Recursion in Angular directives

Angular-Kendo ComboBox placeholder text not working

I have a simple angular-kendo ComboBox on a page without an initially selected value. It should show the placeholder text in that case, but instead it's showing ? undefined:undefined ?
HTML
<select kendo-combo-box ng-model="Project" k-options='projectOptions'></select>
JS
app.controller('MyCtrl', function($scope) {
$scope.projectData = [
{name: 'Bob', value: 1},
{name: 'Tom', value: 2}
];
$scope.projectOptions = {
placeholder: "'Select...'",
dataTextField: 'name',
dataValueField: 'value',
dataSource: {
data: $scope.projectData
}
}
});
Here's a plunker that shows the problem. Can anyone spot the cause?
This used to work in an older version of angular-kendo, but it's not working in the current version.
This is somewhat related to this issue: https://github.com/angular/angular.js/issues/1019
The solution is simple: use an <input> instead of a <select> element:
<input kendo-combo-box ng-model="Project" k-options='projectOptions'/>
app.controller('MyCtrl', function($scope) {
$scope.projectData = [
{name: 'Bob', value: 1},
{name: 'Tom', value: 2}
];
$scope.projectOptions = {
placeholder: "'Select...'",
dataTextField: 'name',
dataValueField: 'value',
dataSource: {
data: $scope.projectData
}
}
});
(demo)
If you are using <Select> instead of <input> you can use simple placeholder="'Project'"
For example:
<select kendo-combo-box="projectComboBox"
k-data-source="projectDataSourceBox"
k-data-text-field="'projectName'"
k-data-value-field="'projectId'"
k-ng-model="Dialog.ProjectId"
k-value-primitive="true"
k-suggest="true"
required="required"
k-auto-bind="false"
k-filter="'contains'"
k-change="projectChangeBox"
style="width: 100%"
placeholder="'Project'">
</select>

Resources