How to expose behavior from Element directive? - angularjs

I ran into a problem adapting the solution from How to expose behavior from a directive with isolated scope?. I wanted to expose my directive as an Element rather than as an Attribute:
Here's a JSFiddle. When you click the first button, which uses the Attribute approach, everything is ok. But the second button uses the Element approach and it gives an error.
Here is the code as well:
HTML:
<div ng-app="main">
<div ng-controller="MyCtrl">Click the first button and everything is ok:
<br>
<button ng-click="callFnInAttribute()">Call Function in Attribute Directive</button>
<br>{{errorViaAttribute}}
<div my-attribute my-fn-via-attribute="fnInCtrlViaAttribute"></div>
<br>
<br>But click the second button and you get an error:
<br>
<button ng-click="callFnInElement()">Call Function in Element Directive</button>
<br>{{errorViaElement}}
<my-element my-fn-via-element="fnInCtrlViaElement"></my-element>
<br>
<br>The only difference is the type of directive used. Why does it work with an Attribute type of directive but not with an Element directive?</div>
</div>
JavaScript:
angular.module("main", []).controller("MyCtrl", function ($scope) {
$scope.callFnInAttribute = function () {
try {
$scope.fnInCtrlViaAttribute();
$scope.errorViaAttribute = "OK";
} catch (anError) {
$scope.errorViaAttribute = "Error: " + anError;
}
};
$scope.callFnInElement = function () {
try {
$scope.fnInCtrlViaElement();
$scope.errorViaElement = "OK";
} catch (anError) {
$scope.errorViaElement = "Error: " + anError;
}
};
}).directive("myAttribute", function () {
return {
require: 'A',
scope: {
myFnViaAttribute: '='
},
controllerAs: 'chartCtrl',
bindToController: true,
controller: function ($scope) {
$scope.myFnViaAttribute = function () {
console.log("myFnViaAttribute called");
}
}
};
}).directive("myElement", function () {
return {
require: 'E',
scope: {
myFnViaElement: '='
},
controllerAs: 'chartCtrl',
bindToController: true,
controller: function ($scope) {
$scope.myFnViaElement = function () {
console.log("myFnViaElement called");
}
}
};
});
This is using the following AngularJS version: https://code.angularjs.org/1.1.0/angular.min.js
How do I correctly expose the behavior from an Element?

I think your error simply comes from the fact that you wrote require instead of restrict in your directives. require is to make sure another directive is present in the same element, restrict is to define the HTML structure of your directive.
.directive("myAttribute", function () {
return {
restrict: 'A', // <-- and not "require"
scope: {
myFnViaAttribute: '='
},
controllerAs: 'chartCtrl',
bindToController: true,
controller: function ($scope) {
$scope.myFnViaAttribute = function () {
console.log("myFnViaAttribute called");
}
}
};
})

Related

Angular 1.x directive scope

I want to transform the scoped variable, like trimming the passed string.
but it shows always as it passed.
here is my sample code,
export function testDirective() {
return {
restrict: 'E',
template: `<a>{{vm.testText}}</a>`,
scope: {
testText: '#'
},
controller: TestController,
controllerAs: 'vm',
bindToController: true
}
}
export class TestController {
testText: string;
constructor(private $scope: angular.IScope) {
// when getting variable, I need to transform the value
$scope.$watch( () => this.testText, (newVal: string) => {
this.testText = newVal.trim() + ' Edited'; // this doesn't affact
});
}
}
why that code is not working?
To make it work I added additional variable(trimmedText), but I don't think this is right approach,
export function testDirective() {
return {
restrict: 'E',
template: `<a>{{vm.trimmedText}}</a>`,
scope: {
testText: '#'
},
controller: TestController,
controllerAs: 'vm',
bindToController: true
}
}
export class TestController {
testText: string;
trimmedText: string;
constructor(private $scope: angular.IScope) {
// when getting variable, I need to transform the value
$scope.$watch( () => this.testText, (newVal: string) => {
this.trimmedText = newVal.trim() + ' Edited'; // it works
});
}
}
Please advice me
#Expert wanna be, using the = sign in the isolated scope of the directive definition sets up two way data binding within the directive.
Check the below code snippet, here's the jsfiddle link.You can find more information about the different types of data binding in directives here
The HTML
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<custom-directive test-text="ctrl.text"></custom-directive>
</div>
</div>
The Angular code
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.controller('CustomDirectiveController', CustomDirectiveController)
.directive('customDirective', customDirective);
function DefaultController() {
var vm = this;
vm.text = 'Hello, ';
}
function customDirective() {
var directive = {
restrict: 'E',
template: `{{vm.testText}}`,
scope: {
testText: '='
},
controller: CustomDirectiveController,
controllerAs: 'vm',
bindToController: true
};
return directive;
}
function CustomDirectiveController() {
var vm = this;
// transforming/updating the value here
vm.testText = vm.testText + 'World!';
}
$scope.$watch( () => this.testText, // <-- use this.textText here
'#' is not right binding, if you want to modify it - use '='.
But using additional variable is actually correct approach.
Another good way for simple transformation is using filter.

controllerAs in directive, functions in controller can't see some variables

I have bellow definition of directive/controller. If you look, there's an onClick function defined. When function is being called, it can see this variable, with ftConditionButton bound to it as described by directive. The thing is, onClick doesn't see conditionButtonController which is against my understanding of JavaScript. Can someone explain to me what I am missing? Right now it looks to me like a new "Class" was created and was given all the methods of original controller.
angular
.module('app')
.directive('ftConditionButton', ftConditionButton);
function ftConditionButton() {
var directive = {
restrict: 'A',
scope: {},
require: ['ftConditionButton'],
templateUrl: 'conditionButton.html',
controller: ConditionButtonController,
controllerAs: 'conditionButtonController',
bindToController: {
ftConditionButton: '&'
}
};
return directive;
}
function ConditionButtonController() {
var conditionButtonController = this;
conditionButtonController.onClick = onClick;
////////////////
function onClick() {
this.ftConditionButton; // this works
conditionButtonController; // conditionButtonController is undefined
}
}
Not sure what you're doing wrong. Seems okay to me.
Few things I can recommend:
1) Make sure you're using angularjs-1.4.
2) Always wrap everything into local function, such that you don't expose anything globally.
(function() {
angular.module('experiment', [])
.controller('MyController', function($scope){
$scope.test = function() {
alert("Test!");
};
});
angular
.module('experiment')
.directive('ftConditionButton', ftConditionButton);
function ftConditionButton() {
var directive = {
template: '<button ng-click="conditionButtonController.onClick()">Hello </button>',
restrict: 'A',
scope: {},
bindToController: {
ftConditionButton: '&'
},
controller: ConditionButtonController,
controllerAs: 'conditionButtonController',
};
return directive;
}
function ConditionButtonController($scope) {
var conditionButtonController = this;
conditionButtonController.onClick = onClick;
function onClick() {
conditionButtonController.ftConditionButton();
}
}
})();
and view:
<div ng-app="experiment">
<div ng-controller="MyController">
<div ft-condition-button="test()" />
</div>
</div>
PS, use JSFiddle next time, to demonstrate your problem.

Call function from directive without isolated scope

I have read this post. However, in that example he calls the controller function after listening on a click event of the element.
How can I achieve calling a controller function when clicking children of the directive element?
<div ng-controller="MyCtrl">
<abc method1="outerMethod('c')" method2="outerMethod2('g')"></abc>
</div>
Directive:
var myApp = angular.module('myApp',[]);
myApp.directive('abc', function() {
return {
restrict: "EA",
replace: true,
template: "<div><p ng-click='clickedP()'>p</p><div ng-click='clickedDiv()'>div</div></div>",
controller: function($scope) {
// how can I call outerMethod if clickedP is executed???
// how can I call outerMethod2 if clickedDiv is executed???
},
controllerAs: "vm",
link: function(scope, element, attrs, vm) {
}
}
});
function MyCtrl($scope) {
$scope.outerMethod = function( a ) {
alert( "you did it" );
}
$scope.outerMethod2 = function( a ) {
alert( "you did it again" );
}
}
Fiddle: http://jsfiddle.net/j93ba7a2/5/
The scope can be used directly without passing attributes. Also, using "controllerAs" on a directive with the same value as the parent controller is a bad idea, since it will overwrite it.
Solution:
var myApp = angular.module('myApp', []);
myApp.directive('abc', function () {
return {
restrict: "EA",
replace: true,
template: "<div><p ng-click='clickedP()'>p</p><div ng-click='clickedDiv()'>div</div></div>",
controller: function ($scope) {
// how can I call outerMethod if clickedP is executed???
$scope.clickedP = function () {
$scope.outerMethod(); // you just call it!
}
// how can I call outerMethod2 if clickedDiv is executed???
$scope.clickedDiv = function () {
$scope.outerMethod2(); //Same way!
}
},
controllerAs: "vm",
link: function (scope, element, attrs, vm) {
/* It would have been better to handle clickedP and
clickedDiv here instead of in the controller, but I'm
trying to avoid confusion by changing as little as
possible of your code. */
}
}
});
function MyCtrl($scope) {
$scope.outerMethod = function (a) {
alert("you did it");
}
$scope.outerMethod2 = function (a) {
alert("you did it again");
}

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);
};
}
};
});

Angular ng-click not executing function inside a custom directive

I have created a custom directive and added a controller to it and a function on the state hamburgerClick.Here is my code:
directivesModule.directive('headerDir', [function () {
var headerDir = {
restrict: 'E',
templateUrl: 'App/scripts/main/directives/header/HeaderDir.html',
replace: true
};
headerDir.controller = ['$state', function ($state) {
$state.hamburgerClick = function() {
var app = $('.application-wrap');
if (app.hasClass('menu-opened')) {
app.removeClass('menu-opened');
}
else {
app.addClass('menu-opened');
}
};
}];
return headerDir;
}]);
<div>
<span class="menu-overlay"></span>
<section class="menu-bar">
<article>
<div class="menu-button" ng-click="hamburgerClick()">
<span class="hamburger-icon"></span>
</div>
<h1 class="logo"></h1>
</article>
</section>
My problem is that for some reason the function does not get executed when I am trying to click on it.ANyone know what I am doing wrong?
Try this!
directivesModule.directive('headerDir', [function () {
return{
restrict: 'E',
templateUrl: 'App/scripts/main/directives/header/HeaderDir.html',
replace: true
controller: function($scope){
$scope.hamburgerClick = function() {
var app = $('.application-wrap');
$('.application-wrap').toggleClass('menu-opened');
};
}
}
}]);
There are several things doubtful in your code
1) You should replace $state with $scope
2) You do not use your directive inside your HTML code. Instead, you refer to a directive named 'article'
3) You use replace:true, which replaces the original content of the directive. Unless you planned on defining your $('.menu-button') as header-dir directive, the call to hamburgerClick will be removed.
Furthermore, you could replace
var app = $('.application-wrap');
if (app.hasClass('menu-opened')) {
app.removeClass('menu-opened');
}
else {
app.addClass('menu-opened');
}
with
$('.application-wrap').toggleClass('menu-opened');

Resources