Angular directive's link function not being called - javascript

I've got a problem with AngularJS directive link function. It's not beeing called and it doesn't throw any error. Also the template in directive's return is not rendering :( Where should be problem? Thank you for answers!
angular.module('sampleApp.game').directive('gameCanvas', function($injector) {
console.log('Directive is working'); // this works,
function linkFn(scope, ele, attrs) {
console.log('Link function doesnt working :('); // but this not :(
};
return {
scope: {},
template: '<div class="blabla"></div>',
link: linkFn
}
});
My html template file
<div class="jumbotron text-center">
<h1>Play a game!</h1>
<p>{{ tagline }}</p>
<div class="game-canvas"></div>
</div>

By default, directives are for Element and Attribute ('EA') only. Define the restrict attribute as 'C'. Best practice is to always define it explicitly.
angular.module('sampleApp.game').directive('gameCanvas', function($injector) {
console.log('Directive is working'); // this works,
function linkFn(scope, ele, attrs) {
console.log('Link function doesnt working :('); // but this not :(
};
return {
scope: {},
restrict: 'C', //'EA' by default
template: '<div class="blabla"></div>',
link: linkFn
}
});
Documented by Angular here - https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object.

Related

Can't access attr data in angular custom directive link function

I'm trying to access attr data defined by a controller in a custom directive's link function.
Here is some simple angular markup:
<div class='barChart'>
{{vm.totals}}
<bar-chart chart-data='vm.totals'></bar-chart
</div>
This is the directive defintion:
angular
.module('app')
.directive('barChart', [Callback])
function Callback() {
return {
restrict: 'E',
replace: false,
scope: {data: '=chartData'},
link: (scope, el, attrs) => {
console.log(scope);
console.log(el);
console.log(attrs.chartData);
}
}
}
When I log scope, I can see the data array in this object as expected, heres a picture:
As you can see the data is the 10 item array at the bottom. The array also shows up in the browser, so the data is there. However, as soon as I change the console.log to log that property:
console.log(scope.data)
The value that gets printed is undefined. I'm trying to access the data in the link function so that I can use d3 to create a visualization. The data is there, but it's undefined as soon as I call .data on scope. Any ideas?
Use $watch to log the data:
angular
.module('app')
.directive('barChart', [Callback])
function Callback() {
return {
restrict: 'E',
replace: false,
scope: {data: '=chartData'},
link: (scope, el, attrs) => {
console.log(scope);
console.log(el);
console.log(attrs.chartData);
//USE $watch
scope.$watch("data", function(value) {
console.log(value);
});
}
}
}
use # in the link function to have one way binding to the controller.
angular
.module('app', [])
.controller('ctrl', function($scope){$scope.totals="1234";})
.directive('barChart', [Callback])
function Callback() {
return {
restrict: 'E',
replace: false,
scope: {data: '#'},
link: function(scope, el, attrs) {
console.log(attrs.chartData);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class='barChart'>
{{totals}}
<bar-chart chart-data='{{totals}}'></bar-chart>
</div>
</div>
you can access vm.totals as scope variable but passed as attribute:
scope: {
chartData: '=' //# reads the attribute value
}
And in your html:
<bar-chart chart-data='vm.totals'></bar-chart>
Now in link function you can access it:
console.log(scope.chartData);
Note that this is one way binding, so if you need two way you need to use = instead.
Update:
If you are using async call you need to use two way binding or watch for the changes in your link function:
scope.$watch('chartData', function(newVal) {
if(newVal) { console.log(scope.chartData) }
}, true);
Note that since it is an array it is better to use collection watch (true as the last arg).

Using service inside directive?

I am learning how to create custom directives.
My service looks like that:
myApp.service('myService',function(){
this.myFunction=function(myParam){
// do something
}
});
Here is my directive:
myApp.directive('myDirective',function(myService){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="myService.myFunction(param)">Do action</button>',
}
});
In HTML, when I use <my-directive my-param="something"></my-directive> it properly renders as a button. However when I click it, myService.myFunction, doesn't get executed.
I suppose I am doing something wrong. Can someone give me a direction?
I guess this has something to do with the directive's scope.
The service wont be available directly inside the template. You'll have to use a function attached to the directive's scope and call the service function from within this function.
myApp.directive('myDirective',function(myService){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="callService(param)">Do action</button>',
link: function(scope, element, attrs) {
scope.callService = function() {
myService.myFunction();
}
}
}
});
It doesn't work because in your example a directive doesn't actually know what is myService. You have to explicitly inject it e.g.:
myApp.directive('myDirective', ['myService', function(myService){ ... }]);
See also this question or this question.
You should use a controller to do all DOM-modifications.
See this plunkr: https://plnkr.co/edit/HbfD1EzS0av5BG6NgtIv?p=preview
.directive('myFirstDirective', [function() {
return {
'restrict': 'E',
'controller': 'MyFirstController',
'controllerAs': 'myFirstCtrl',
'template': '<h1>First directive</h1><input type="text" ng-model="myFirstCtrl.value">'
};
}
You can inject the service in the controller and then call that function inside your template:
Inject myService into controller:
myApp.controller("ctrl", function($scope, myService) {
$scope.doService = function(myParam) {
return myService.myFunction(myParam);
};
});
Call doService method of the controller inside your template:
myApp.directive('myDirective',function(){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="doService(param)">Do action</button>',
}
});

Call function defined in isolated scope in directive

Am I missing something here?
Shouldn't I be able to access the function showHelp() from the template ng-click ?
I've been fighting with this for hours and I can't get it right
angular.module('starter.directives', [])
.directive('errorMessage', function() {
return {
restrict: 'E',
require: '^form',
scope: true,
template:'<button ng-click="showHelp()" class="button icon ion-android-alert button-outline button-assertive"></button>',
link: function(scope, element, attrs, formCtrl) {
scope.showHelp = function(){
console.log('hello');
}
}
};
});
There's nothing wrong with your code. I am guessing you just forgot to include the module in your HTML. Something that looks like
<div ng-app="starter.directives">
....
</div>
Here's your code that is working properly in jsFiddle
You should add it in controller not link function. link function runs after everything was rendered.
angular.module('starter.directives').directive('errorMessage', function() {
return {
restrict: 'E',
scope: true,
template:'<button ng-click="showHelp()" class="button icon ion-android-alert button-outline button-assertive">Test</button>',
controller: function($scope) {
$scope.showHelp = function(){
console.log('hello');
}
}
};
});
I think the problem is that you reinitialize your module by passing [] here
angular.module('starter.directives', []) If you pass a second parameter to angular.module, it would think that you pass an array of dependecies and will reinit the module.

Dynamic NG-Controller Name

I want to dynamically specify a controller based on a config that we load. Something like this:
<div ng-controller="{{config.controllerNameString}}>
...
</div>
How do I do this in angular? I thought this would be very easy, but I can seem to find a way of doing this.
What you want to do is have another directive run before anything else is called, get the controller name from some model remove the new directive and add the ng-controller directive, then re-compile the element.
That looks like this:
global.directive('dynamicCtrl', ['$compile', '$parse',function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-ctrl'))(scope);
elem.removeAttr('dynamic-ctrl');
elem.attr('ng-controller', name);
$compile(elem)(scope);
}
};
}]);
Then you could use it in your template, like so:
<div dynamic-ctrl="'blankCtrl'">{{tyler}}</div>
with a controller like this:
global.controller('blankCtrl',['$scope',function(tyler){
tyler.tyler = 'tyler';
tyler.tyler = 'chameleon';
}]);
There's probably a way of interpolating the value ($interpolate) of the dynamic-ctrl instead of parsing it ($parse), but I couldn't get it to work for some reason.
I'm using it in ng-repeat, so this is improved code for loops and sub objects:
Template:
<div class="col-xs6 col-sm-5 col-md-4 col-lg-3" ng-repeat="box in boxes">
<div ng-include src="'/assets/js/view/box_campaign.html'" ng-dynamic-controller="box.type"></div>
</div>
Directive:
mainApp.directive('ngDynamicController', ['$compile', '$parse',function($compile, $parse) {
return {
scope: {
name: '=ngDynamicController'
},
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem, attrs) {
elem.attr('ng-controller', scope.name);
elem.removeAttr('ng-dynamic-controller');
$compile(elem)(scope);
}
};
}]);
Personally the 2 current solutions here didn't work for me, as the name of the controller would not be known when first compiling the element but later on during another digest cycle. Therefore I ended up using:
myapp.directive('dynamicController', ['$controller', function($controller) {
return {
restrict: 'A',
scope: true,
link: function(scope, elem, attrs) {
attrs.$observe('dynamicController', function(name) {
if (name) {
elem.data('$Controller', $controller(name, {
$scope: scope,
$element: elem,
$attrs: attrs
}));
}
});
}
};
}]);

AngularJS: Objects inside nested directives getting undefined?

So, I have two directives, one that has a template file, which contains another directive of the same type.
The first directive looks like:
.directive('billInfo', function () {
return {
// scope: true,
scope: {
obj: '='
},
restrict: 'E',
templateUrl: 'views/templates/bill-info.html',
link: function (scope, element, attrs) {
scope.status = scope.obj.getStatus();
scope.bill = scope.obj;
}
}
})
And the template is pretty simple, something like;
<h4>
<span class="glyphicon glyphicon-cutlery">
{{bill.getTable()}}
</span>
<small><span class="time"></span></small>
<div class="btn-group bill-btn">
<bill-btns billobj="bill"></bill-btns>
</div>
</h4>
The directive for billBtns looks like:
.directive('billBtns', function () {
return {
scope: {
billobj: '='
},
restrict: 'E',
template: '<div><div>koko{{status}}</div></div>',
link: function (scope, element, attrs) {
console.log(scope, scope.billobj);
scope.status = scope.billobj.getStatus();
}
}
})
The problem is unexpected: scope.billobj turns out to be undefined. When I console log scope from within the link function of the billBtns directive, all seems ok: I can see billobj inside scope.
What is going on here? Am I doing something fundamentally wrong here?
EDIT: Template for billInfo
<div draggable ng-repeat="(index, bill) in getEnq()" bill="bill" id="bill-{{bill.orderCode}}" class="container panel panel-default bill float-{{index%2}}" style="width:300px;" data-created="{{bill.getCreatedOn()}}">
<bill-info obj="bill"></bill-info>
</div>
I believe I've come to a solution, but I'm uncertain as to if this is the right practice. Here's what the new billBtns directive looks like:
.directive('billBtns', function () {
return {
restrict: 'E',
template: '<div><div>koko{{status}}</div></div>',
link: function (scope, element, attrs) {
console.log(scope, scope.$parent.obj);
scope.status = scope.$parent.bill.getStatus();
}
}
})
And that solves the problem. My suspicion is this, if we look at the billInfo directive again, I do something like:
scope.bill = scope.obj; // woah?
I'd like to understand more about why this happens and why I can access scope.$parent.bill from a nested directive but not scope.$parent.obj without getting typeerrors. Or maybe thats just the way to cascade scopes.

Categories