angular scope variable not updating inside directive - angularjs

I am using angular ng-simplepaginator and i had implemented custom directive for selectbox.
directive
.directive('sel',['onChangeService', function(onChangeService) {
return {
template: '<select class="span2" ng-model="itemsPerPage" ng-options="obj as obj for obj in itemsPerPageList" data-style="btn-primary" bs-select></select>',
restrict: 'E',
scope: {
itemsPerPage: '=',
totalitems: '=totalitems'
},
link: function (scope, elem, attrs) {
scope.itemsPerPageList = ['All','10','20','30','40'];
scope.itemsPerPage = scope.itemsPerPageList[1];
},
controller: function ($scope, $element) {
$scope.$watch('itemsPerPage', function () {
onChangeService.changePager($scope, $scope.itemsPerPage, $scope.totalitems.length);
});
}
};
}]);
service
tellerApp.factory('onChangeService', function ($rootScope, Pagination) {
return {
changePager: function($scope,itemsPerPage,totalLength) {
$scope.pagination = Pagination.getNew(itemsPerPage);
$scope.pagination.numPages = Math.ceil(totalLength/$scope.pagination.perPage);
}
}
});
I am able to calling service inside directive, but the page values not updating.
for example, when i choose 20 items, the values are not updating. (i.e pagination.numPages, pagination.page values are not updating)
<ul class="pagination">
<li ng-class="pagination.firstPageDisabled()">First</li>
<li>«</li>
<li ng-repeat= "n in pagination.range(pagination.numPages, pagination.page, pagination.page+pagination.gap)" ng-class="{active: n == pagination.page}">
{{n + 1}}
</li>
<li>»</li>
<li ng-class="pagination.lastPageDisabled()">Last</li>
</ul>

Related

Why is 'this' different things in two different functions?

I have a problem where this isn't what I expect it to be, but I can't figure out why after hours of ripping my hair out. From what I understand this should be the same this in both functions, but it isn't. This causes my custom select to not update the label when you select something else in the dropdown because getCurrentOption always returns undefined since it tries to access .currentOption from the wrong this object.
Can someone explain what is happening here? And how to make it so that I pass the same this object to the two functions?
co-select.html:
<div class="co-select-form-control">
<label class="co-inset-label" ng-bind="label"></label>
<ul class="list-reset" ng-class="{'co-select': !label, 'co-select-labeled': label}">
<li ng-click="co.toggleSelect(this)" class="co-select-option clickable">
<span ng-bind="co.getCurrentOption(this) || default"></span>
<ul ng-show="co.isSelectToggled(this)" class="list-reset bg-light co-select-dropdown">
<li ng-repeat="option in list" ng-if="option !== co.getCurrentOption(this)"
ng-click="co.selectOption(this, option)" ng-bind="option" class="co-select-option"></li>
</ul>
<span class="co-select-icon">
<i class="icon icon-keyboard-arrow-{{co.isSelectToggled(this) ? 'up' : 'down'}}"></i>
</span>
</li>
</ul>
</div>
co-select directive:
coForms.directive('coSelect', [function() {
return {
restrict: 'E',
scope: {
default: '=',
list: '=',
label: '#'
},
controller: 'CoFormsCtrl',
controllerAs: 'co',
templateUrl: 'app/views/components/co-forms/co-select.html',
link: function(scope, element, attrs) {
}
};
}]);
The controller:
coForms.controller('CoFormsCtrl', [function() {
var coForms = this;
/* View functions */
coForms.toggleSelect = function(select) {
select.isToggled = !select.isToggled;
};
coForms.isSelectToggled = function(select) {
return select.isToggled ? true : false;
};
coForms.selectOption = function(select, option) {
select.currentOption = option;
console.log(select);
};
coForms.getCurrentOption = function(select) {
console.log(select);
return select.currentOption;
};
}]);
The console.log from within coForms.getCurrentOption shows this to be:
While the console.log from within coForms.selectOption shows this to be:
How I use this directive:
<co-select list="['option 1', 'option 2', 'option 3']" default="'option 1'"></co-select>
In Angular expressions this is a special word and refers to the scope on which the expression is being evaluated. Since ng-repeat creates a new scope, scope inside and outside it will be different.
Using this in expressions can be rarely required, and this case isn't an exception.
That's because ng-repeat creates a new scope (if i'm correct, it extends the parent scope), your selectOption is called inside ng-repeat so this represents that scope.
You shouldn't be using controller functions this way (passing this as parameter). You should make a variable "select" in your scope and use that.
This should work (see this plunkr: http://plnkr.co/edit/Javqd1zoKbubHEUD2Ea9?p=preview ) :
coForms.directive('coSelect', [function() {
return {
restrict: 'E',
scope: {
default: '=',
list: '=',
label: '#'
},
controller: 'CoFormsCtrl',
controllerAs: 'co',
templateUrl: 'app/views/components/co-forms/co-select.html',
link: function(scope, element, attrs) {
}
};
}]);
coForms.controller('CoFormsCtrl', ['$scope', function($scope) {
var coFormsCtrl = this;
coFormsCtrl.select={
isToggled: true,
currentOption:$scope.default
};
/* View functions */
coFormsCtrl.toggleSelect = function() {
coFormsCtrl.select.isToggled = !coFormsCtrl.select.isToggled;
};
coFormsCtrl.isSelectToggled = function() {
return coFormsCtrl.select.isToggled ? true : false;
};
coFormsCtrl.selectOption = function(option) {
coFormsCtrl.select.currentOption = option;
console.log(coFormsCtrl.select);
};
coFormsCtrl.getCurrentOption = function() {
console.log(coFormsCtrl.select);
return coFormsCtrl.select.currentOption;
};
}]);
And the template:
<div class="co-select-form-control">
<label class="co-inset-label" ng-bind="label"></label>
<ul class="list-reset" ng-class="{'co-select': !label, 'co-select-labeled': label}">
<li ng-click="co.toggleSelect()" class="co-select-option clickable">
<span ng-bind="co.getCurrentOption() || default"></span>
<ul ng-show="co.isSelectToggled()" class="list-reset bg-light co-select-dropdown">
<li ng-repeat="option in list" ng-if="option !== co.getCurrentOption()"
ng-click="co.selectOption(option)" ng-bind="option" class="co-select-option"></li>
</ul>
<span class="co-select-icon">
<i class="icon icon-keyboard-arrow-{{co.isSelectToggled() ? 'up' : 'down'}}"></i>
</span>
</li>
</ul>
</div>

Directive that returns template needs scope variable

The template that loads content("html/script template") for each cell:
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index" >
<div ng-init="values = dbo.get4(attobj.key); key = attobj.key; template = attobj.template || getAttributeTemplate(dbo.clazz + attobj.key);">
<div plain-template="template">
</div>
</div>
</td>
I need the directive to access the value "template" from its directive "plain-template"
Directive:
app.directive('plainTemplate', function($parse) {
return {
templateUrl: 'testTemplate',
function (scope, element, attrs) {
console.log($parse(attrs.plainTemplate));
}
};
});
You can use something like
app.directive('plainTemplate', function($parse) {
return {
templateUrl: 'testTemplate',
scope: {
plainTemplate:"="
},
link: function (scope, element, attrs) {
console.log(scope.plainTemplate));
}
};
});
and when you call it in html your variable should be like
<plain-template test-template="anything"> </plain-template>
this is isolated scope. You can see more at https://docs.angularjs.org/guide/directive

Dynamic Controller and templateUrl inject in an Angular Directive

I have troubles to find a solution to make a fully dynamic directive. I use the angular-gridster library to make an overview of tiles for a dashboard page. I want to dynamicly load some specific tiles by a flexible directive.
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles">
<div class="box" ng-controller="tileGrid_TileController">
<eg-tile template-url={{tile.tileEngine.tempUrl}}
controller-name={{tile.tileEngine.tileCtrl}}>
</eg-tile>
</div>
</li>
</ul>
</div>
I have created the egTile directive :
(function () {
function implementation() {
return {
restrict: 'E',
transclude: true,
scope: true,
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: function (elem, attrs) {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
};
}
var declaration = [implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
This directive will work if I use a fixed string in the egTile directive like
<eg-tile template-url="string_url" controller-name= "string_ctrl"></eg-tile>
but I want to dynamicly select the controller and templateUrl.
I already tried to use the $parse and $observe service but without succes.
Is this even possible to make the directive so flexible ?
Thanks in advance
I found a solution for my problem.....
I used 2 extra directives that will provide the controller-string and the templateUrl-string for the "flexible directive" egTile.
One for creating the controller-string :
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99999,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-controller'))(scope);
elem.removeAttr('eg-parse-controller');
elem.attr('controller-name', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseController', declaration);
}());
And one for creating the template-string:
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99998,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-template'))(scope);
elem.removeAttr('eg-parse-template');
elem.attr('template-url', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseTemplate', declaration);
}());
Than I can use it as :
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles" ng-controller="tileGrid_TileController">
<eg-tile tile="tile"
eg-parse-template=tile.tileEngine.tempUrl
eg-parse-controller='tile.tileEngine.tileCtrl'>
</eg-tile>
</li>
</ul>
with the directive definition :
(function () {
function implementation($parse) {
return {
restrict: 'E',
transclude: true,
scope: {
tile : '='
},
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: 'app/module/tileGrid/view/tileTemplate.html',
link: function(scope, element, attrs) {
scope.getContentUrl = function() {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
}
};
}
var declaration = ['$parse' , implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
With a tileTemplate.html :
<div class="box">
<div class="box-header">
<h3>{{ vm.tile.tileParams.title }}</h3>
<div class="box-header-btns pull-right">
<a title="Remove widget" ng-click="vm.removeTile(tile)">
<i class="fa fa-trash"></i>
</a>
</div>
</div>
<div class="box-content">
<div ng-include="getContentUrl()"></div>
</div>
</div>
With this aproach I have fully access to the tile that I passed to the egTile Directive in the dynamic loaded controllers and dynamic loaded views.
All remarks are welcome.

AngularJS nested directives, inner directive's ng-click is not firing

This is a problem I'm having in my app and I am about to reproduce it in a simple example:
<div f-outer>
<div f-inner dat="dat">
<a href="javascript:" ng-click="run(dat, $event)">
Click Me
</a>
</div>
</div>
Clicking "Click Me", I'd expect the run function defined on the f-inner directive to execute. But it's not.
.directive('fOuter', function () {
return {
link: function (scope) {
scope.dat = { x: 1, y: 2 };
console.log('fOuter.link');
}
};
})
.directive('fInner', function () {
return {
restrict: 'A',
scope: {
dat: '='
},
link: function (scope) {
console.log('fInner.link');
scope.run = function (dat, $event) {
console.log('fInner.scope.run()', dat, $event);
};
}
};
})
I also
Here's the example:
http://jsfiddle.net/5QtJs/2/
I also added translude with no luck:
transclude: true,
template: "<div ng-transclude></div>",
http://jsfiddle.net/5QtJs/3/
You are on the right track with using transclude, presuming you want to include arbitrary content. In your example transclude is unnecessary. The ng-click call you are making is not finding your 'run' method in your directive. (Since, run is used in angular modules, I would advise against using in code). I've refactored your directive below.
<div ng-controller="Ctrl3">
<div f-outer>
<div f-inner dat="data">Will not show since using 'replace'</div>
</div>
</div>
.directive('fOuter', function () {
return {
link: function (scope) {
scope.dat = { x: 1, y: 2 };
console.log('fOuter.link');
}
};
})
.directive('fInner', function () {
return {
restrict: 'A',
replace: true,
scope: {
dat: '='
},
template: '<div>' +
'<a ng-click="run(dat, $event)">Click Me</a>' +
'</div>',
link: function (scope) {
console.log('fInner.link');
scope.run = function (dat, $event) {
console.log('fInner.scope.run()', dat, $event);
};
}
};
});

dynamically adding directives in ng-repeat

I am trying to dynamically add different directives in an ng-repeat however the output is not being interpreted as directives.
I've added a simple example here: http://plnkr.co/edit/6pREpoqvmcnJJWzhZZKq
Controller:
$scope.colors = [{name:"red"}, {name: "blue"}, {name:"yellow"}];
Directive:
app.directive("red", function () {
return {
restrict: 'C',
template: "RED directive"
}
});
Html:
<ul>
<li ng-repeat="color in colors">
<span class="{{color.name}}"></span>
</li>
</ul>
How do I make angular pick up the directive specified in the class that is output via ng-repeat?
I know this is an old question, but google brought me here, and I didn't like the answers here... They seemed really complicated for something that should be simple. So I created this directive:
***** NEW CONTENT *****
I've since made this directive more generic, supporting a parsed (the typical angular value) "attributes" attribute.
/**
* Author: Eric Ferreira <http://stackoverflow.com/users/2954747/eric-ferreira> ©2016
*
* This directive takes an attribute object or string and adds it to the element
* before compilation is done. It doesn't remove any attributes, so all
* pre-added attributes will remain.
*
* #param {Object<String, String>?} attributes - object of attributes and values
*/
.directive('attributes', function attributesDirective($compile, $parse) {
'use strict';
return {
priority: 999,
terminal: true,
restrict: 'A',
compile: function attributesCompile() {
return function attributesLink($scope, element, attributes) {
function parseAttr(key, value) {
function convertToDashes(match) {
return match[0] + '-' + match[1].toLowerCase();
}
attributes.$set(key.replace(/([a-z][A-Z])/g, convertToDashes), value !== undefined && value !== null ? value : '');
}
var passedAttributes = $parse(attributes.attributes)($scope);
if (passedAttributes !== null && passedAttributes !== undefined) {
if (typeof passedAttributes === 'object') {
for (var subkey in passedAttributes) {
parseAttr(subkey, passedAttributes[subkey]);
}
} else if (typeof passedAttributes === 'string') {
parseAttr(passedAttributes, null);
}
}
$compile(element, null, 999)($scope);
};
}
};
});
For the OP's use case, you could do:
<li ng-repeat="color in colors">
<span attributes="{'class': color.name}"></span>
</li>
Or to use it as an attribute directive:
<li ng-repeat="color in colors">
<span attributes="color.name"></span>
</li>
***** END NEW CONTENT ******
/**
* Author: Eric Ferreira <http://stackoverflow.com/users/2954747/eric-ferreira> ©2015
*
* This directive will simply take a string directive name and do a simple compilation.
* For anything more complex, more work is needed.
*/
angular.module('attributes', [])
.directive('directive', function($compile, $interpolate) {
return {
template: '',
link: function($scope, element, attributes) {
element.append($compile('<div ' + attributes.directive + '></div>')($scope));
}
};
})
;
For the specific case in this question, one can just rewrite the directive a bit to make it apply the directive to a span by class, as so:
angular.module('attributes', [])
.directive('directive', function($compile, $interpolate) {
return {
template: '',
link: function($scope, element, attributes) {
element.replaceWith($compile('<span class=\"' + attributes.directive + '\"></span>')($scope));
}
};
})
;
Then you can use this anywhere and select a directive by name dynamically. Use it like so:
<li ng-repeat="color in colors">
<span directive="{{color.name}}"></span>
</li>
I purposely kept this directive simple and straightforward. You may (and probably will) have to reword it to fit your needs.
i faced with the same problem in one of my projects and you can see how i solve this problem on jsfiddle
HTML:
<div class="page-wrapper" ng-controller="mainCtrl">
<div class="page">
<h3>Page</h3>
<ul>
<li ng-repeat="widget in widgets"><div proxy="widget" proxy-value="{{widget}}"></div></li>
</ul>
</div>
JS:
var app = angular.module('app',[]);
app.controller('mainCtrl', ['$scope', '$q', 'widgetAPI', function($scope, $q, widgetAPI) {
$scope.widgets = [];
widgetAPI.get().then(
function(data) {
$scope.widgets = data;
},
function(err) {
console.log("error", err);
}
);}])
.service('widgetAPI', ['$q', function($q) {
var api = {};
api.get = function() {
//here will be $http in real app
return $q.when(
[
{
component: 'wgtitle',
title: "Hello world",
color: '#DB1F1F',
backgroundColor: '#c1c1c1',
fontSize: '32px'
},
{
component: 'wgimage',
src: "http://cs425622.vk.me/v425622209/79c5/JgEUtAic8QA.jpg",
width: '100px'
},
{
component: 'wgimage',
src: "http://cs425622.vk.me/v425622209/79cf/S5F71ZMh8d0.jpg",
width: '400px'
}
]
);
};
return api;}])
.directive('proxy', ['$parse', '$injector', '$compile', function ($parse, $injector, $compile) {
return {
replace: true,
link: function (scope, element, attrs) {
var nameGetter = $parse(attrs.proxy);
var name = nameGetter(scope);
var value = undefined;
if (attrs.proxyValue) {
var valueGetter = $parse(attrs.proxyValue);
value = valueGetter(scope);
}
var directive = $injector.get(name.component + 'Directive')[0];
if (value !== undefined) {
attrs[name.component] = value;
}
var a = $compile(directive.template)(scope);
element.replaceWith(a);
}
}}])
.directive('wgtitle', function() {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<h1 style="color:{{widget.color}}; font-size:{{widget.fontSize}}; background:{{widget.backgroundColor}}" >{{widget.title}}</h1>',
link: function(scope, element, attrs) {
}
}})
.directive('wgimage', function() {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<img style="width:{{widget.width}}" src="{{widget.src}}"/>',
link: function(scope, element, attrs) {
}
}});
I hope it will usefull.
I don't think you'll be able to just assign the directive as a class name - you would need to run this through $compile again, which would be heading down the path towards recursion errors.
One possible solution is outlined at: AngularJS - how to have a directive with a dynamic sub-directive
If it works for your use case, you can use templates instead:
<div ng-repeat='template in inner' ng-include='template'></div>

Resources