Replace $scope with this by turning on ControllerAs - javascript

I thought to replace $scope with this keyword in my sample Angular code base and in turn to switch to using ControllerAs syntax.
But in turn this does not seem to work now.
I have a list of countries in my controller and in my custom directive whenever a country name is clicked , I show the map of the respective country.
<body ng-controller="appCtrl as vm">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Welcome to the world of directives!</a>
</div>
<ul class="nav navbar-nav">
<li ng-repeat="countryTab in vm.countries" ng-click="vm.itemClicked(countryTab)" style="cursor:pointer">
<a>{{countryTab.label}}</a>
</li>
<br>
</ul>
</div>
</nav>
<data-country-tab-bar country="vm.selectedCountry" ng-if="vm.selectedCountry">
<strong><i>Check out our cool new directive!</i></strong>
</data-country-tab-bar>
<script>
var app = angular.module('app',[]);
app.controller('appCtrl',function($scope,$http){
this.countries = [{
id: 1,
label: 'Italy',
coords: '41.29246,12.5736108'
}, {
id: 2,
label: 'Japan',
coords: '37.4900318,136.4664008'
}, {
id: 3,
label: 'USA',
coords: '37.6,-95.665'
}, {
id: 4,
label: 'India',
coords: '20.5937,78.9629'
}];
this.itemClicked = function(value){
this.selectedCountry = value;
}
});
And in my directive , I just bind the country object that is the part of my DDO's isolated scope , to that of the controller's.
app.directive('countryTabBar',function(){
return {
restrict: 'E',
transclude:true,
replace:true,
$scope:{
country: '='
},
template: '<div>'+
' <div><strong>{{country.label }}</strong> : {{ country.coords}}</div>'+
' <br/>'+
' <img ng-src="https://maps.googleapis.com/maps/api/staticmap?center={{country.coords}}&zoom=4&size=800x200"> '+
' <br/><br/>'+
' <div ng-transclude></div>'+
'</div>',
}
});
</script>
</body>
But , I can see the transcluded string Check out our cool new directive! but I can't see the map.
There is no error as such in console.
Please help.

I think the problem is related to:
this.itemClicked = function(value){
this.selectedCountry = value;
}
this.selectedCountry in a function declared so in JavaScript will refer to the current function, not the controller (parent function) as you expect.
Solution (ES5):
var vm = this;
this.itemClicked = function(value){
vm.selectedCountry = value;
}
Solution (ES6):
this.itemClicked = value => this.selectedCountry = value;
Additionally, the directive scope syntax seems to be incorrect:
$scope:{
country: '='
},
Should be:
scope:{
country: '='
},
Hope this helps.

Related

Passing argument to custom directive in AngularJS

I have a navbar that shows few country names and when you click on them respective map should show up.
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Welcome to the world of directives!</a>
</div>
<ul class="nav navbar-nav">
<li ng-repeat="countryTab in countries" ng-clicked="itemClicked(countryTab)" style="cursor:pointer">
<a>{{countryTab.label}}</a></li>
</ul>
</div>
</nav>
And the array of countries for now is hard-coded .
var app = angular.module('app',[]);
app.controller('appCtrl',function($scope){
// Countries
$scope.countries = [{
id: 1,
label: 'Italy',
coords: '41.29246,12.5736108'
}, {
id: 2,
label: 'Japan',
coords: '37.4900318,136.4664008'
}, {
id: 3,
label: 'USA',
coords: '37.6,-95.665'
}, {
id: 4,
label: 'India',
coords: '20.5937,78.9629'
}];
});
The custom directive that should show the respective map for now is like :
<div>
<country-tab-bar></country-tab-bar>
</div>
And
app.directive('countryTabBar',function(){
return {
restrict: ';E',
template: '<div>'+
' <div>Italy</div>'+
' <br/>'+
' <img ng-src="https://maps.googleapis.com/maps/api/staticmap?center=41.29246,12.5736108&zoom=4&size=800x200"> '+
'</div>',
link : function(scope){
scope.itemClicked = function(value){
}
}
}
});
As it is hard coded coordinates , it only shows one map of Italy. But I want to make it to show respective maps passing coordinates .
Also the name in the div should change to reflect the current country .
How to achieve the same ?
Please provide a necessary explanation.
You can achieve the desired like below
you need to pass the country label and country coords from the html to directive first.
<country-tab-bar coords="countryTab.coords" country="countryTab.label"></country-tab-bar>
Receive the values in directive scope.
scope: {
coords: '=coords',
country: '=country',
}
in the link section use these scope members and update the template URL. append this to the element (this is the html tag where the directive is applied). and finally compile it.
var app = angular.module('app',[]);
app.controller('appCtrl',function($scope){
// Countries
$scope.countries = [{
id: 1,
label: 'Italy',
coords: '41.29246,12.5736108'
}, {
id: 2,
label: 'Japan',
coords: '37.4900318,136.4664008'
}, {
id: 3,
label: 'USA',
coords: '37.6,-95.665'
}, {
id: 4,
label: 'India',
coords: '20.5937,78.9629'
}];
});
app.directive('countryTabBar', function($compile){
return {
restrict: ';E',
scope: {
coords: '=coords',
country: '=country',
},
link : function(scope,element){
var template = '<div ng-click="show()">'+scope.country+'</div><img ng-src="https://maps.googleapis.com/maps/api/staticmap?center='+scope.coords+'&zoom=4&size=800x200">';
element.append(template);
$compile(element.contents())(scope);
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app" ng-controller="appCtrl">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Welcome to the world of directives!</a>
</div>
<ul class="nav navbar-nav">
<li ng-repeat="countryTab in countries" style="cursor:pointer">
<country-tab-bar coords="countryTab.coords" country="countryTab.label"></country-tab-bar>
</li>
</ul>
</div>
</nav>
</body>

How to implement here hiding and displaying items in the list?

I found a code that outputs a multi-level list into Angular here
http://jsfiddle.net/c4Kp8/
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);
}
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="test">
<ul>
<li nested-item ng-repeat="item in items">{{item.title}}</li>
</ul>
</div>
How to implement here hiding and displaying items in the list? Only Angular not jquery....
Hope you know that, you can make nested ng-repeat without special directive for it :-) ?
You can use for example angular-ui.bootstrap.collapse, or some custom solutions.
Solutions:
Angular UI Bootrap
Googled custom solution
Use ng-show / ng-hide / ng-if
https://docs.angularjs.org/api/ng/directive/ngShow
https://docs.angularjs.org/api/ng/directive/ngHide
https://docs.angularjs.org/api/ng/directive/ngIf
Maybe a treeview is more adequate than multiples imbricated ng-repeat
http://ngmodules.org/modules/angular.treeview

dynamically dropdowns through angularjs

Having one select tag in html now i want to make select tag dynamic through angularjs so that i can get drop downs from one select options and also want to give different ng-options for every new drop down created
"<div>
<label>dropdown1</label>
<select ng-options=''></select>
</div>"
To be honest, your question is for me a little unclear, but it may help you:
<div ng-repeat="object in myObjects">
<label>{{object.name}}</label>
<select ng-options="object.myOptions"></select>
</div>
this in js:
function AppCtrl ($scope) {
$scope.myObjects = {
"Select1": {
"name": "dropdown1",
"myOptions": [
"one",
"two"
]
}, ....
var app =angular.module('pof', []);
app.controller('myController2', function($scope){
$scope.statuses = [{
id: 1,
name: "First Value"
}, {
id: 2,
name: "Second Value"
}, {
id: 3,
name: "Third Value"
}, {
id: 4,
name: "Fourth Value"
}];
$scope.selected_status = 3;
})
app.directive('bsDropdown', function ($compile) {
return {
restrict: 'E',
scope: {
items: '=dropdownData',
doSelect: '&selectVal',
selectedItem: '=preselectedItem'
},
link: function (scope, element, attrs) {
var html = '';
switch (attrs.menuType) {
case "button":
html += '<div class="btn-group"><button class="btn button-label btn-info">Action</button><button class="btn btn-info dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>';
break;
default:
html += '<div class="dropdown"><a class="dropdown-toggle" role="button" data-toggle="dropdown" href="javascript:;">Dropdown<b class="caret"></b></a>';
break;
}
html += '<ul class="dropdown-menu"><li ng-repeat="item in items"><a tabindex="-1" data-ng-click="selectVal(item)">{{item.name}}</a></li></ul></div>';
element.append($compile(html)(scope));
for (var i = 0; i < scope.items.length; i++) {
if (scope.items[i].id === scope.selectedItem) {
scope.bSelectedItem = scope.items[i];
break;
}
}
scope.selectVal = function (item) {
switch (attrs.menuType) {
case "button":
$('button.button-label', element).html(item.name);
break;
default:
$('a.dropdown-toggle', element).html('<b class="caret"></b> ' + item.name);
break;
}
scope.doSelect({
selectedVal: item.id
});
};
scope.selectVal(scope.bSelectedItem);
}
};
});
<link href="http://st.pimg.net/cdn/libs/bootstrap/2.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://code.angularjs.org/1.4.8/angular.js"></script>
<script src = "http://st.pimg.net/cdn/libs/jquery/1.8/jquery.min.js">
</script>
<script src = "http://st.pimg.net/cdn/libs/bootstrap/2/js/bootstrap.min.js">
</script>
<body ng-app="pof">
<div ng-controller="myController2 as myCtrl2">
<select ng-init="selected_status = statuses[1].id" ng-model="selected_status" ng-options="status.id as status.name for status in statuses"></select>
<!--<bs-dropdown data-menu-type="button" select-val="selected_status = selectedVal"
preselected-item="selected_status" data-dropdown-data="statuses"></bs-dropdown> --> Selected Value : {{selected_status}}
</div>
</body>
I don't know what your model looks like but maybe something like this:
<div ng-repeat="item in items">
<label>{{item.label}}</label>
<select ng-options="item.options"></select>
</div>
In your controller you would have an array $scope.items that contain all your dropdowns/select elements and their options.
$scope.items = [
{'label':'Item 1','options':{"option 1.1","option 1.2"}},
{'label':'Item 2','options':{"option 2.1","option 2.2"}}
];

How do i implement a sliding filter on ng-repeat in angularjs

I am trying to implement a simple sliding filter in angular, but my ng-repeat does not seeem to be responding
The fiddle for this is here http://jsfiddle.net/zn4b89n0/1/
I have tried the following
HTML
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.4/ui-bootstrap-tpls.min.js"></script>
<script src="https://rawgit.com/rzajac/angularjs-slider/master/dist/rzslider.js"></script>
<div ng-app="myapp">
<div ng-controller="TestController as vm">
<p>Price 1: {{vm.priceSlider1.value}}</p>
<rzslider rz-slider-model="vm.priceSlider1.value" rz-slider-floor="vm.priceSlider1.floor" rz-slider-ceil="vm.priceSlider1.ceil"></rzslider>
<ul ng-repeat="friend in vm.friends ">
<li>{{friend.name | filter:filterFn}}</li>
</ul>
</div>
</div>
JS
var myApp = angular.module('myapp', ['rzModule', 'ui.bootstrap']);
myApp.controller('TestController', TestController);
function TestController($scope, $timeout) {
var vm = this;
vm.priceSlider1 = {
floor: 80,
ceil: 100,
value: 85
};
vm.friends = [
{ name: "Peter", age: 20 },
{ name: "Pablo", age: 55 },
{ name: "Linda", age: 20 },
{ name: "Marta", age: 37 },
{ name: "Othello", age: 20 },
{ name: "Markus", age: 32 }
];
vm.filterFn = function(friend)
{
// Do some tests
if(friend.age >= vm.priceSlider1.value )
{
return true; // this will be listed in the results
}
return false; // otherwise it won't be within the results
};
vm.refreshSlider = function () {
$timeout(function () {
$scope.$broadcast('rzSliderForceRender');
});
};
}
How do i get this to work, thank you for any and all assistance
You should use your filter in ng-repeat and execute the filter:
<ul ng-repeat="friend in vm.friends | filter: vm.filterFn() ">
<li>{{friend.name}}</li>
</ul>
You can simplify the filter function:
vm.filterFn = function()
{
return function(item){
//return the whether age attribute of each item (friend) is greater than your value
return item['age'] >= vm.priceSlider1.value;
}
};
Here you can try the updated fiddle: http://jsfiddle.net/Ln9cgrve/
I hope that solves your problem...
Looks like the problem in ngRepeat part. It should be
<ul ng-repeat="friend in vm.friends | filter:vm.filterFn">
<li>{{friend.name}}</li>
</ul>
Note, filter is in ngRepeat directive and also filter function should be referred as vm.filterFn.

Why doesn't directive work if I put name as an attribute rather than a tag?

In the example from the Angular documentation, a directive can be used by putting its name as an attribute in a <div>. The example given is
<div ng-controller="Controller">
<div my-customer></div>
</div>
with the js looking like
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
However, in a similar example, over here, if I change the html code from <tree> to <div tree>, the code no longer works.
Why not?
The code from JS Fiddle:
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily">
<p>{{ family.name }}</p>
</tree>
</div>
</div>
var module = angular.module('myapp', []);
module.controller("TreeCtrl", function($scope) {
$scope.treeFamily = {
name : "Parent",
children: [{
name : "Child1",
children: [{
name : "Grandchild1",
children: []
},{
name : "Grandchild2",
children: []
},{
name : "Grandchild3",
children: []
}]
}, {
name: "Child2",
children: []
}]
};
});
module.directive("tree", function($compile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child">{{family.name}}</tree>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents, transclude);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
tree {
margin-left: 20px;
display: block;
}
The restrict option is used to specify how a directive can be invoked on the page. There are four different ways to invoke a directive, so there are four valid options for restrict:
'A' - attribute - <span ng-sparkline></span>
'E' - element - <ng-sparkline></ng-sparkline>
'C' - class - <span class="ng-sparkline"></span>
'M' - comment - <!-- directive: ng-sparkline -->
In your case its not working because it is defined as restrict 'E' - element.
It's because of the restrict option in the directive.
Here it is set to e which means to match only the element name.
More https://docs.angularjs.org/guide/directive

Categories