Angular JS directive, calling another controller - angularjs

I'm trying to implement a simple <pagination> directive in Angular JS, and having trouble getting it to communicate with another controller to get it to react on changes to the current page.
Here's what my code looks like:
paginationDirective.js
app.controller('paginationCtrl', function ($scope) {
this.init = function (attrs) {
$scope.items = [
{'page': '1', class: 'current'},
{'page': '2', class: ''},
{'page': '3', class: ''},
{'page': '4', class: ''},
{'page': '5', class: ''}
];
};
});
app.directive('pagination', function () {
return {
controller: 'paginationCtrl',
restrict: 'E',
scope: {
count: '#',
page: '#'
},
templateUrl: 'views/partials/pagination.html',
link: function (scope, element, attrs, paginationCtrl) {
paginationCtrl.init(attrs);
}
}
});
pagination.html
<div class="pagination-centered">
<ul class="pagination">
<li class="arrow unavailable">«</li>
<li ng-repeat="item in items" class="{{ item.class }}">
{{ item.page }}
</li>
<li class="arrow">»</li>
</ul>
</div>
somepage.html
<div class="content">
<table>...data for page x...</table>
<pagination onpage=" <!-- Run somepageCtrl.page(x) --> "></pagination>
</div>
somepageCtrl.js
app.controller('somepageCtrl', function($scope, myService){
$scope.currentPage = 1;
$scope.init = function () {
myService.loadData($scope);
};
$scope.navigate = function(page) {
console.log('Navigating to page ' + page);
}
$scope.init();
});
I'm still new to AngularJS, so I'm not entirely sure what I should do to get the pagination directive to tell somepageCtrl to navigate to the next page.
I would like to be able to pass a function that needs to be executed when the user clicks an item in the pagination directive as an attribute on the directive.
Can anyone help and nudge me in the right direction?

I'm going to answer my own question, thanks to Esteban Felix for pointing me into the right direction.
I defined the navigate() method on somepageCtrl:
$scope.navigate = function (page) {
console.log('Lets go to page ' + page);
};
then simply added the function to
<pagination on-change="navigate"></pagination>
The method then gets assigned in the app.directive definition like so:
scope: {
count: '#',
page: '#',
onChange: '&onChange'
},
On the app.controller for paginationCtrl, I created a callback function:
$scope.callback = function (page) {
console.log('Turn to page ' + page);
$scope.onChange()(page);
}
and updated the markup for pagination.html to call the above callback with ng-click like so:
{{ item.page }}

You can use the experession callback & to call your controller back from a directive.
pagination directive
app.directive('pagination', function () {
return {
controller: 'paginationCtrl',
restrict: 'E',
scope: {
count: '#',
page: '#',
onPageChange: '&'
},
templateUrl: 'views/partials/pagination.html',
link: function (scope, element, attrs, paginationCtrl) {
paginationCtrl.init(attrs);
// grab function pointer so it will be called correctly with ng-click
scope.onPageChange = scope.onPageChange();
}
}
});
somepage.html
<div class="content">
<table>...data for page x...</table>
<pagination on-page-change="sompageCtrl.navigate"></pagination>
</div>
pagination.html
<div class="pagination-centered">
<ul class="pagination">
<li class="arrow unavailable">«</li>
<li ng-repeat="item in items" class="{{ item.class }}">
{{ item.page }}
</li>
<li class="arrow">»</li>
</ul>
</div>

Related

How to generate event listener function in angularjs directive?

How to generate event listener function in angularjs directive?
For something like this:
Directive:
app.directive('myDirective', () => {
return {
scope: {
myDict: '=',
},
templateUrl: 'directives/my-directive',
};
});
HTML:
<!-- my-directive.html -->
<li ng-repeat="(key, value) in myDict">
<!-- Will not work -->
<a onclick="function_written_in_frontend({{key}})">{{value}}</a>
</li>
Use it:
<my-directive myDict="{'a': 1, 'b': 2, 'c': 3}"></my-directive>
What I hope is that it might generate:
<li>
<a onclick="function_written_in_frontend('a')">1</a>
<a onclick="function_written_in_frontend('b')">2</a>
<a onclick="function_written_in_frontend('c')">3</a>
</li>
But I cannot. And ng-click might not be the solution here.
Thx in advance.
Update:
If I updated my Directive like this:
app.directive('myDirective', () => {
return {
scope: {
myDict: '=',
},
controller: function ($scope, $element) {
$scope.function_written_in_frontend = function(mykey) {
console.log(mykey);
}
},
templateUrl: 'directives/my-directive',
};
});
And it gives me:
{{key}}
{{key}}
{{key}}
{{key}}
try ng-click instead of onclick
<a ng-click="function_written_in_frontend(key)">{{value}}</a>

Controller function not called from directive template in AngularJS

Directive template (items.html)
<li ng-repeat="item in itemCart">
{{item.title}} <br>
{{item.category}} &nbsp
{{ formatCurrencyFunction({cost: item.price}) }}
</li>
This custom directive is used in Second.html
<h1>
This is Second.
{{header}}
</h1>
<hr>
<ul>
<items-list item-cart="items" format-currency-function="formatPrice(cost)"></items-list>
</ul>
The code for Controller is:
myApp.directive("itemsList", function(){
return{
templateUrl:"contents/Views/Directives/Items.html",
replace: true,
scope:{
itemCart: "=",
formatCurrencyFunction: "&"
},
restrict:"EACM" // E-Element A-Attribute C-Class M-Comments
}
})
myApp.controller('secondController', ['$scope', '$log', '$http','$routeParams', function($scope, $log, $http, $routeParams) {
$scope.formatPrice = function(price){
return "₹ "+parseFloat(price).toFixed(2);
};
$scope.header = 'Second ' + ($routeParams.num || "");
$scope.testSecond = "Second";
$http.get("/items")
.success(function(data){
$scope.items = data;
});
}]);
The function formatPrice is not called from the directive Items.html
What has to be corrected in my code to get it working?
Inside the directive's link function, you'll want to call the method like so:
scope.someFn({arg: someValue});
myApp.directive("itemsList", function(){
return{
templateUrl:"contents/Views/Directives/Items.html",
replace: true,
scope:{
itemCart: "=",
formatCurrencyFunction: "&formatCurrencyFunction"
},
link:function(scope, element, attrs){
scope.formatPrice({arg: 35}); //scope.someFn({arg: 35});
},
restrict:"EACM" // E-Element A-Attribute C-Class M-Comments
}
})
Got this working by using ng-repeat in the Second.html page instead of the directive.
Directive template (items.html)
<li>
{{item.title}} <br>
{{item.category}} &nbsp
{{ formatCurrencyFunction({cost: item.price}) }}
</li>
This custom directive is used in Second.html
<h1>
This is Second.
{{header}}
</h1>
<hr>
<ul>
<items-list item="item" format-currency-function="formatPrice(cost)" ng-repeat="item in items"></items-list>
</ul>
Code in JS file
myApp.directive("itemsList", function(){
return{
templateUrl:"contents/Views/Directives/Items.html",
replace: true,
scope:{
item: "=",
formatCurrencyFunction: "&"
},
restrict:"EACM" // E-Element A-Attribute C-Class M-Comments
}
})
Inside the directive's link function, you need to call the method by applying some event to that attribute
Controller function 'formatCurrencyFunction()' can't be called by just placing it in an expression.
To use the function you need to call it based on an even, in your case ng-init will work.
<li ng-repeat="item in itemCart" ng-init="formatCurrencyFunction({cost: item.price})">
{{item.title}} <br>
{{item.category}}
</li>

How to evaluate stored expression in AngularJS $scope

Please help me in this issue. Following is the the code.
HTML:
<div ng-controller="ctrl">click here</div>
JS:
app.controller('ctrl', function($scope) {
$scope.true_link = "http://google.com";
$scope.link = "{{ true_link }}";
});
Result:
<div ng-controller="ctrl">click here</div>
Expectation:
<div ng-controller="ctrl">click here</div>
Replace {{ link }} by {{ true_link }} in HTML will solve this problem. But I have to use this way. How can I evaluate expression in $scope.link content again? Please help me. Thanks.
Update
Look like facebook, I have two wall pages: User page and Actor page. They have same template structure and process (append, remove element etc...) after bussiness function such as changeAvatar(), changeCover(), post() etc... So I create 'homepage' based directive:
JS
app.directive('homepage', function() {
return {
restrict: 'A',
templateUrl: 'homepage.html',
controller: 'homepageCtrl'
};
});
app.controller('homepageCtrl', function($scope) {
$scope.changeAvatar() = ...;
$scope.post() = ...;
});
and two extend controllers:
app.controller('userCtrl', function($scope, $http) {
$http.({...}).success((data){ $scope.username = data.username })
$scope.menu = [
{
title: "foo-user"
link: "/u/{{ username }}/foo-user"
}
{
title: "bar-user"
link: "/u/{{ username }}/bar-user"
}
]
});
app.controller('actorCtrl', function($scope) {
$http.({...}).success((data){ $scope.actorname = data.actorname })
$scope.menu = [
{
title: "foo-actor"
link: "/u/{{ actorname }}/foo-actor"
}
{
title: "bar-actor"
link: "/u/{{ actorname }}/bar-actor"
}
]
});
HTML
homepage.html
<section>
<header>
<ul class="menu">
<li ng-repeat="_menu in menu">
<a href="{{ _menu.link }}">
{{ _menu.title }}
</a>
</li>
</ul>
</header>
<main>
content...
</main>
</section>
User page:
<div homepage ng-controller="userCtrl"></div>
Actor page:
<div homepage ng-controller="actorCtrl"></div>
Two pages menu has same HTML structure & effect, but differ in items. I wanna define menu item in extended controller (userCtrl, actorCtrl) and print them by ng-repeat. The problem is evaluate $scope.menu.link content.
Solution
I found solution: using $scope.$eval (https://docs.angularjs.org/guide/expression).
In userCtrl, the $scope.menu[i].link is dynamic content because included username - received from ajax call. I can update $scope.menu[i].link in $http.success() by using foreach. But I think using $scope.$eval help me auto update wherever I want easier.
So, the code is:
JS
app.controller('userCtrl', function($scope, $http) {
$http.({...}).success((data){ $scope.username = data.username })
$scope.menu = [
{
title: "foo-user"
link: "'/u/' + username + '/foo-user'"
show: 'true'
}
{
title: "bar-user"
link: "'/u/' + {{ username }} + '/bar-user'"
show: 'username == "lorem"'
}
]
});
HTML
homepage.html
<section>
<header>
<ul class="menu">
<li
ng-repeat="_menu in menu"
>
<a
ng-href="{{$parent.$eval(_menu.link)}}"
ng-show="$parent.$eval(_menu.show)"
>
{{_menu.title}}
</a>
</li>
</ul>
</header>
<main>
content...
</main>
</section>
Create a directive, and use $eval to parse the expression:
app.directive('a', function (){
return {
restrict : 'E',
link: function(scope, element, attr){
element.attr('href',scope.$eval(attr.href));
}
}
});
app.controller('ctrl', function($scope) {
$scope.true_link = "http://google.com";
$scope.link = $scope.true_link; // you need a copy of `$scope.true_link` here
});
<div ng-controller="ctrl"><a ng-href="link">click here</a></div>
$scope.link = "{{ true_link }}"; this is just a string, nothing else, so it will render as such.
Don't be confused by the curly brackets.
You can complicate things by using $compile, eval, etc... or you can simply assign the true_link value to the link variable.
UPDATE
You have a problem in your ng-repeat directive. This:
<li ng-repeat="_menu in menu">
{{ menu.title }}
</li>
Should be this (note _menu):
<li ng-repeat="_menu in menu">
{{ _menu.title }}
</li>
Also, there's no need to use templating in your controller, so this:
$scope.menu = [
{
title: "foo-user"
link: "/u/{{ username }}/foo-user"
}
{
title: "bar-user"
link: "/u/{{ username }}/bar-user"
}
]
can be this:
$scope.menu = [
{
title: "foo-user"
link: "/u/" + $scope.username + "/foo-user"
}
{
title: "bar-user"
link: "/u/" + $scope.username + "/bar-user"
}
]
Why do you want to use {{link}} instead of {{true_link}}? What exactly the issue you've run into that "you have to use this way"? If you can explain this in details, may we can figure out a better solution.

Pass object to custom angular bootstrap ui tooltip

Im trying to pass and object to a custom angular-ui bootstrap tooltip component.
My code so far is a new directive:
angular.module('ui.bootstrap.korv', [ 'ui.bootstrap.tooltip' ])
.directive('korvPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { title: '#', content: '#', placement: '#', animation: '&', isOpen: '&', species: '='},
templateUrl: 'korv.html'
};
})
.directive('korv', [ '$tooltip', function ($tooltip) {
return $tooltip('korv', 'korv', 'click');
}]);
and the the template:
<script type="text/ng-template" id="korv.html">
<div class="tooltip {{ placement }}" ng-class="{ in: isOpen(), fade: animation() }">
<div class="tooltip-arrow"></div>
<div class="tooltip-inner">obj is {{content}} obj.a is {{content.a}}</div>
</div>
and in the view:
<li korv="{a:1234}">
outputs:
obj is {a:1234} obj.a is
So the object I pass converts to a json string and I can not access its fields. Using tooltipHtmlUnsafe is not an option here.
I tried changing content: '#' to content: '=' but that doesn't work.
So how can I pass an object to tooltip?
This is not possible due to the implementation of angular bootstrap ui tooltip. My solution was to create a new directive:
angular.module('app').directive('speciesInfo', function ($log, $timeout) {
return {
restrict: 'A', // only activate on element attribute
scope: {
species: "=speciesInfo"
},
link: function (scope, elem, attrs) {
var showPromise;
elem.on('mouseenter', function () {
showPromise = $timeout(function () {
elem.children('.popover').show();
}, 500);
});
elem.on('mouseleave', function () {
$timeout.cancel(showPromise);
elem.children('.popover').hide();
});
},
templateUrl: 'species-info.html'
}
});
now it is easy to style the tooltip:
<div class="popover right in fade">
<div class="arrow"></div>
<div class="popover-inner">
<div class="popover-title text-center">
{{species.vernacularName}} <img class="small-species" ng-src="{{species.iconFileName}}"/>
</div>
<div class="popover-content">
<em> {{species.scientificName}}</em>
</div>
</div>

Adding directive dynamically using ng-click

I am working on a modal for a client. I have created a directive that works great, the problem is that one modal is made ahead each time it is used like..
What I have
<div ng-repeat="item in items">
<a data-toggle="modal" data-target="{{item.id}}">Click</a>
<my-dialog element-id="item.id">
<h1>This is the body of the modal</h1>
</my-dialog>
</div>
This works great for a small amount of modals but we are using a very large number of modals. So I would like to add the directive at runtime, something closer to...
What I want...
<div id="warning"></div>
<div ng-repeat="item in items">
<a data-toggle="modal" data-target="{{item.id}}" ng-click="showModal(item)">Click</a>
</div>
...
// inside controller
$scope.showModal = function(item){
$http.get('/someUrl').success(function(data){
var result = $compile('<my-dialog element-id="'+item.id+'">'+data+'</my-dialog>').($scope);
$("#warning").append(result);
});
}
$scope.hideModal = function(){
$( "#warning" ).empty();
}
This of course isn't working yet. Also it doesn't feel like the best way. This would allow me to remove the directive once it has been closed.
Please include a plunker or equivalent for the check.
One way you could do this is to use ng-repeat with your items, then call $scope.$apply() after you push a new item to the list. The HTML could look like this ...
<div ng-repeat="item in items">
<span dialog>
<a class="dialog-anchor">{{item.name}}</a>
<div class="dialog-body">{{item.id}}</div>
</span>
</div>
... and the directive like this
.directive('dialog', [function () {
return {
scope: {
id: '#elementId',
}
, link: function (scope, el, attrs) {
var body = $(el).find('.dialog-body').detach();
$(el).find('.dialog-anchor').on('click', function () {
$('body').append(body);
});
}};
}])
... and the controller like this
.controller('app', ['$scope', function ($scope) {
$scope.items = [
{name: 'first', id: 001},
{name: 'second', id: 002}
];
setTimeout(function () {
$scope.items.push({name: 'three', id: 003});
if (!$scope.$$phase) $scope.$apply();
}, 2000);
}])
Here's the plunker... http://plnkr.co/edit/2ETbeCKGcHW3CJCfD9d7?p=preview. You can see the $scope.$apply call in the setTimeout where I push a new item to the array.
Try this:
var result = $compile('<my-dialog element-id="'+item.id+'">'+data+'</my-dialog>')($scope);

Resources