AngularJS custom directive scope not reflecting - angularjs

I have a custom directive called crust:
JS:
.directive('crust', function(){
return{
restrict: 'EA',
replace: true,
scope: {
datasource: '='
},
templateUrl: '../../configurator/partials/crust.html'
}
})
HTML template (crust.html):
<li data-ng-repeat="type in datasource.types">
<input type="radio"
name="{{datasource.id}}"
data-ng-class="radio-selector"
data-ng-true-value="true"
value="true"
data-ng-model="type.selected"
data-ng-change="updatePizza(type)"
id="{{type.id}}">
<label for="{{type.id}}"> <span></span>
<h2 data-ng-bind="type.name"></h2>
<p data-ng-bind="type.description"></p>
</label>
</li>
The Model (crustTypes) is pulled via a service from this JSON:
{
"id": "crt",
"types": [{
"id": "crt1",
"name": "original",
"description": "Our traditional scratch-made crust",
"price": "5",
"selected":"false"
}, {
"id": "crt2",
"name": "thin",
"description": "A light crispier crust",
"price": "6",
"selected":"false"
}, {
"id": "crt3",
"name": "fresh pan",
"description": "A thick buttery crust",
"price": "7",
"selected":"false"
}, {
"id": "crt4",
"name": "stuffed",
"description": "Two layers of original crust",
"price": "8",
"selected":"false"
}]
}
The directive is being invoked in the HTML like so:
<ul>
<crust data-datasource="crustTypes" data-datavalue="pcrustType"></crust>
</ul>
The looping is working fine, and ng-repeat is rendering the list properly. The problem is that I want to assign datasource.id as the common name of the radio group, and due to some reason, datasource.id is coming up as undefined. Consequently, the name is not being assigned and the user is being allowed to enter multiple selections.
If instead I pass type to updatePizza(item) it comes up fine. Its just the parent model that's not being displayed
If I try to return datasource through updatePizza(), it is still coming up as undefined.
I'm sure I'm missing something basic here. Help!
Here is a Plunker of the code

Replace name="{{datasource.id}}" with name="{{$parent.datasource.id}}"

Related

Using ng-if and ng-repeat correctly

I have a rudimentary understanding of AngularJS and have a couple (possibly stupid) questions about ng-if and ng-repeat.
If I have a ng-repeat that looks like this:
For all containers without titles, would the ng-repeat produce those containers and not show them? Or do they simply don't exist? The reason I ask is I've tried to replace ng-if with ng-show, but it does not produce the same outcome of "hiding" containers that do not have titles.
Is there a way to code a ng-if to say if the next container has no title, then do something. I tried something like ng-if="item.title=='' && $index+1", without any luck.
Any suggestions?
My data looks like this:
"_sections": [{
"_bootstrap_cells": 6,
"_count": 2,
"visible": true,
"columns": [{
"fields": [{
"name": "type_of_account",
"type": "field"
}, {
"name": "routing_transit_number",
"type": "field"
}]
}, {
"fields": [{
"name": "type_of_payment",
"type": "field"
}, {
"name": "check_digit",
"type": "field"
}]
}],
"caption": "Direct Deposit",
"id": "b456b9d2137ac340177c36328144b0ef"
}, {
"_bootstrap_cells": 12,
"_count": 1,
"visible": true,
"columns": [{
"fields": [{
"name": "account_number",
"type": "field"
}, {
"name": "account_title",
"type": "field"
}, {
"name": "financial_institution_name",
"type": "field"
}]
}],
"caption": "",
"id": ""
}
}]
The first section has two columns and a bootstrap cell value of 6 for each column, the second section only has one column and a bootstrap cell of 12. Since the second doesn't have a caption, I want it to be appended to the first section, but keep both sections' bootstrap formatting.
If containers is an array then it does not have a title property. You need to check the title on each item.
Also note that ng-if will not hide the content, but remove it or not insert it into the DOM. The counterpart of ng-show is ng-hide.
angular.module('appModule', [])
.controller('MyController', [function() {
this.containers = [{
aaa: 1,
title: 'One'
}, {
aaa: 2,
title: ''
}, {
aaa: 3,
title: 'Three'
}]
}]);
angular.bootstrap(window.document, ['appModule'], {
strictDi: true
});
<div ng-controller="MyController as myCtrl">
<div ng-repeat="item in myCtrl.containers track by $index">
<div ng-if="item.title!=''">{{$index}}. {{item.title}}</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>

Watch on a new attribute which is attached on a nested objects, but not firing

How could I watch on a new attribute which is attached on a nested objects.
As you can see, each row of the table is an item of fares array.
And I manually add a field order_amount to each fare
<td class="col-lg-1 col-md-1 col-sm-1">
<h4>
<input ng-model="fare.order_amount" type="number" min="0" ng-init="fare.order_amount=0">
</h4>
</td>
Once a user change the order amount, the shopping cart should be updated immediately.
However, the whole model is complicated. All fares are belongs_to flight_list
$scope.$watchCollection('flight_list', function(newVal, oldVal) {
console.log($scope);
});
So I watched on flight_list collection, but it didn't fire, when I increase the order amount.
Furthermore, there are about 500 fares on each page.
I'm afraid that the performance will be really poor.
Any good idea?
[
{
"from_airport": {
"id": 11,
"city_id": 7,
"code": "TPE",
"name": "桃園國際機場",
"created_at": "2016-07-06T18:19:11.483Z",
"updated_at": "2016-07-06T18:19:11.483Z"
},
"fares": [
{
"id": 5241,
"flight_sku_id": 1311,
"cabin_class": "ECONOMY",
"price": 15038.0,
"specs": {
"flight_number": "IT-911",
"duration": 7,
"return_departure_time": "06:00",
"return_arrive_time": "13:00"
},
"created_at": "2016-07-06T18:20:14.292Z",
"updated_at": "2016-07-06T18:20:14.292Z",
"cabin_code": "J",
"eligibility": "ADULT",
"description": null,
"ticket_type": "GROUP",
"saleable": true,
"cost": 14345.0
},
...
You can set 3rd argument to true for deep watching a collection
$scope.$watchCollection('flight_list', function(newVal, oldVal) {
console.log($scope);
}, true);

ng-repeat does not react to given limitTo or filter

I have a directive that displays subcategories of a feature list.
Problem is, all items are displayed, regardles of the filters i use for the ng-repeat.
I use a filter to only show items with a certain category_uid and i also use a limitTo.
app.directive('featureCategory', function() {
return {
restrict: 'A',
transclude: true,
link: function($scope) {
$scope.featureCountBuffer = $scope.featureCount;
$scope.listLength = Object.keys($scope.featureList).length;
},
scope: {
featureList: '=',
featureCount: '#',
categoryId: '#'
},
template:
'<li>featureCountBuffer : {{featureCountBuffer}} / listLength {{listLength}} / categoryId {{categoryId}}</li>' +
'<check-item ng-repeat="feature in featureList | filter:{category_uid:categoryId} | limitTo: featureCountBuffer" feature="feature"></check-item>'
}
});
This shows above 40 items thou it starts with the li element showing featureCountBuffer is 5 andcategory_uid being 1 which only applies to 8 items.
Does anyone see my Mistake here?
The featureList looks like this:
{
"1": {
"uid": "1",
"title": "foo",
"category_uid": "1",
"checked": false
},
"2": {
"uid": "2",
"title": "bar",
"category_uid": "1",
"checked": false
},
"3": {
"uid": "3",
"title": "foobar",
"category_uid": "2",
"checked": false
},
"4": {
"uid": "4",
"title": "barfoo",
"category_uid": "2",
"checked": false
},
"5": {
"uid": "5",
"title": "barbar",
"category_uid": "3",
"checked": false
}
...
}
I found the Problem.
Angulars ng-repeat works with objects of objects but the filters do not.
You can either write your own filters to fix this or just use an array of objects.
I used lodash's _.toArray to convert mine. My Changes:
[..]
link: function($scope) {
[..]
$scope.featureList = _.toArray($scope.featureList);
},
[..]
Youcan read about it in more detail in this article.
Thanks for posting this! I found an alternative solution to use the $index property:
<ul>
<li ng-repeat="(id, value) in test" ng-if="$index < 3" >
{{ $index }} {{ id }} {{ value }}
</li>
</ul>

Apply jQuery code after directive template is available on DOM

I have the following Directive:
appDirective.directive('sidebar', function () {
var directiveDefinitionObject = {
//return {
restrict: 'E',
replace: true,
template: '<li ng-repeat="m in menu" ng-class="{\'dropdown\':m.submenu, \'open\':m.open}">' +
'<a href="{{m.url}}" ng-class="{\'dropdown-toggle\':m.submenu}" ng-attr-data-toggle="{{m.type}}">' +
'<i class="{{m.image}}"></i>' +
' {{m.name}}' +
'<b class="caret" ng-if="m.submenu"></b>' +
'</a>' +
'<ul ng-if="m.submenu" class="dropdown-menu">' +
'<li ng-repeat="s in m.submenu"><a ng-class="{\'active\':s.active}" href="{{s.url}}"><i class="{{s.image}}"></i> {{s.name}}</a></li>' +
'</ul>' +
'</li>',
link: function (scope, elem, attrs) {
scope.menu = [
{
"name": "Dashboard",
"url": "/manage/dashboard/index",
"image": "fa fa-dashboard",
"type": "dropdown",
"open": dashboardOpen,
"submenu": [
{
"name": "Overview",
"active": dashboard_overview_active,
"url": "/manage/dashboard/index",
"image": "fa fa-circle-o"
},
{
"name": "System Performance",
"active": dashboard_overview_nodes,
"url": "/manage/dashboard/nodes",
"image": "fa fa-tasks"
},
{
"name": "Query Performance",
"url": "/manage/dashboard/index",
"image": "fa fa-bolt"
}
]
},
{
"name": "Data Integration",
"url": "/manage/dataintegration/index",
"image": "fa fa-table"
},
{
"name": "User Management",
"url": "/manage/users/index",
"type": "dropdown",
"image": "fa fa-users",
"open": usersOpen,
"submenu": [
{
"name": "Users",
"active": users_users,
"url": "/manage/users/index",
"image": "fa fa-user"
},
{
"name": "Roles & Permissions",
"active": users_roles,
"url": "/manage/users/roles",
"image": "fa fa-lock"
},
{
"name": "Organizations",
"active": users_orgs,
"url": "/manage/users/organizations",
"image": "fa fa-globe"
}
]
},
{
"name": "Logs and Alerts",
"url": "/manage/logger/index",
"image": "fa fa-bars"
}
];
scope.$watch('menu', function(val) {
console.log(val);
$('.dropdown').on('show.bs.dropdown', function (e) {
$(this).find('.dropdown-menu').first().stop(true, true).slideDown();
});
$('.dropdown').on('hide.bs.dropdown', function (e) {
$(this).find('.dropdown-menu').first().stop(true, true).slideUp();
});
})
}
}
return directiveDefinitionObject;
});
I've added some jQuery code to the $watchfunction that I want to run after the directive has actually drawn the template. However the $watch fires before the elements are added to the DOM.
This is not the way we should work with angular. When working with angular, we should attach event handler though directives like ng-click and ng-change.
In your case, when the model changes, there is no guarantee that the view already finishes rendering. The view also listens to changes to your model as your code does by using $watch. When the model changes, it will notify the view and your code separately. Usually, there is a trick by using $timeout to schedule the work for the next cycle. But I think we could take advantage of jQuery event delegation to deal with dynamic elements.
Try setting replace: false and use event delegation. Don't need $watch
elem.on('show.bs.dropdown','.dropdown', function (e) {
$(this).find('.dropdown-menu').first().stop(true, true).slideDown();
});
elem.on('hide.bs.dropdown','.dropdown', function (e) {
$(this).find('.dropdown-menu').first().stop(true, true).slideUp();
});
Using $watch in your case has another downside that multiple event handlers may be added to the same element because every time the model changes, this code will be run again.

AngularJS: Preselecting Options on Multiple Select

I'm trying to preselect options on my multiselect. In order to make my case clear, I've made a JSFiddle.
<select ng-model="properties" id="props" multiple
ng-options="option.name group by option.category for option in options"></select>
Unfortunately, I am bound by the way the object is received, so I guess I need the ng-options attribute.
Does anybody have an idea how I can get both 'Captain America' and 'Dr. Doom' selected on load?
Try changing your controller code to:
function TestCtrl($scope) {
var myOptions = [{
"id": "4",
"name": "Captain America",
"categoryId": "1",
"category": "Heroes"
}, {
"id": "5",
"name": "Iron Man",
"categoryId": "1",
"category": "Heroes"
}, {
"id": "10",
"name": "Magneto",
"categoryId": "2",
"category": "Vilains"
}, {
"id": "9",
"name": "Dr. Doom",
"categoryId": "2",
"category": "Vilains"
}];
$scope.options = myOptions;
$scope.properties = [myOptions[0], myOptions[3]];
}
Explanation: you are setting your selected options (properties) to different instances of the objects than the ones that compose the full list. Use the same object references as shown above.
You are super close, two changes. Define the attributes in your controller like this
$scope.properties = ["Captain America", "Dr. Doom"];
And add a small piece to your ng-options like this
ng-options="option.name as option.name group by option.category for option in options"
Your option.name as will determine what the saved feature looks like in $scope.properties

Resources