Call directive controller methods from outside - javascript

I write a directive that provide a tab functionality, I use latest version of AngularJS (v1).
In my directive I have a controller that expose an api to a children directives, the scope is isolated in all directives:
Parent Directive
scope: {},
controller: function($scope) {
$scope.values = [];
this.addValue = function(value) {
$scope.values.push(value);
}
}
Child Directive on link function
scope: {},
transclude: true,
template: '<div ng-transclude></div>',
replace: true,
link: function(scope, element, attr, parentCtrl)
{
parentCtrl.addValues('test')
}
In child directive I have a custom html with own controller:
TestCtrl
function TestCtrl($scope){
var vm = this;
... other logic
}
Implementation
<parent>
<child>
<div ng-controller="TestCtrl as t">
<button ng-click="addValues('new_test')"></button>
</div>
</child>
</parent>
I need to call "addValues" method (inside directive controller) on click on my button.
How to organise the code to make this?

var module = angular.module('app', []);
module.controller('root', function ($scope) {
$scope.test = function () {
console.log('i am clicked');
}
});
module.component('child', {
template: '<button type="button" data-ng-click="$ctrl.click()">Click me</button>',
controller: myController,
bindings: {
onClick: '&'
}
});
function myController() {
var ctrl = this;
ctrl.click = function () {
this.onClick();
}
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js"></script>
</head>
<body data-ng-app="app">
<div data-ng-controller="root">
<child data-on-click="test()"></child>
</div>
</body>
</html>
It is just an example
Here you can read more.
Hope this helps.
MAy be it will be usefull to read about angular's best practice here

Related

pass dynamic controller to angular directive

i am familiar with the syntax of controller & name but i'm trying to create a generic directive that will get a list of items and for each item i need to specify a controller.
This is my main directive:
function controlPanel() {
var directive = {
restrict: 'E',
replace: true,
scope: {
controlPanelItems: "=sbControlPanelItems"
},
templateUrl: 'control-panel.html',
link: link
};
return directive;
function link(scope, element) {
}
}
Here is the directive template:
<sb-control-panel-item ng-repeat="controlPanelItem in controlPanelItems"
sb-title="controlPanelItem.title"
sb-template-url="controlPanelItem.templateUrl"
sb-control-panel-item-controller="controlPanelItem.controller"></sb-control-panel-item>
My issue is with the sb-control-panel-item-controller attribute.
Angular throws exception when i'm passing variable, it work's great when i'm passing simple string (the name of the controller).
Here is the code of the control-panel-item directive:
function controlPanelItem() {
var directive = {
restrict: 'E',
replace: true,
scope: {
title: '=sbTitle',
templateUrl: '=sbTemplateUrl'
},
templateUrl: 'control_panel_item.html',
controller: '#',
name: 'sbControlPanelItemController',
link: link
};
return directive;
function link(scope, iElement, iAttributes, controller) {
}
}
Maybe there is a way to inject the controller through the link function and then i'll just pass it through the scope?
You can use the $controller service to instantiate whatever controller dynamically inside the directive, check this plunkr.
Just bear in mind that if you wanted to specify a controller statically now, you would need to enclose it in single quotes.
Basically the code would be like:
function MainCtrl() {
this.firstCtrl = 'FirstCtrl';
this.secondCtrl = 'SecondCtrl';
}
function FirstCtrl() {
this.name = 'First Controller';
}
function SecondCtrl() {
this.name = 'Second Controller';
}
function fooDirective() {
return {
scope: {
ctrl: '='
},
template: '<div>{{foo.name}}</div>',
controller: ['$controller', '$scope', function($controller, $scope) {
var foo = $controller($scope.ctrl, {$scope: $scope});
return foo;
}],
controllerAs: 'foo',
link: function ($scope, $element, $attrs, $ctrl) {
console.log($scope.ctrl);
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('MainCtrl', MainCtrl)
.controller('FirstCtrl', FirstCtrl)
.controller('SecondCtrl', SecondCtrl);
and this would be the HTML
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.5.8" data-semver="1.5.8" src="https://code.angularjs.org/1.5.8/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl as main">
<h1>
Test
</h1>
<foo-directive ctrl="main.firstCtrl">
"name: " {{foo.name}}
</foo-directive>
<foo-directive ctrl="main.secondCtrl">
{{foo.name}}
</foo-directive>
</body>
</html>
========================================================================
WRONG OLD ANSWER
From this blog entry seems to be an undocumented property that allows you to do exactly what you need.
function FirstCtrl() {
this.name = 'First Controller';
}
function SecondCtrl() {
this.name = 'Second Controller';
}
function fooDirective() {
return {
scope: {},
name: 'ctrl',
controller: '#',
controllerAs: 'foo',
template: '<div></div>',
link: function ($scope, $element, $attrs, $ctrl) {
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('FirstCtrl', FirstCtrl)
.controller('SecondCtrl', SecondCtrl);
So all you need to do in your directive is add a property name linked to the attribute you will use with the name of your controller.
<foo-directive ctrl="FirstCtrl"></foo-directive>
<foo-directive ctrl="SecondCtrl"></foo-directive>
If your directive, as per your question, needs to be from a property rather than a string, use {{}} notation:
<sb-control-panel-item ng-repeat="controlPanelItem in controlPanelItems"
sb-title="controlPanelItem.title"
sb-template-url="controlPanelItem.templateUrl"
sb-control-panel-item-controller="{{controlPanelItem.controller}}"></sb-control-panel-item>

How to call a directive's function from the controller on Angular

I have a directive that controls a personalized multiselect. Sometimes from the main controller I'd like to clear all multiselects. I have the multiselect value filling a "filter" bidirectional variable, and I am able to remove content from there, but when doing that I also have to change some styles and other content. In other words: I have to call a method belonging to the directive from a button belonging to the controller. Is that even posible with this data structure?:
(By the way, I found other questions and examples but their directives didn't have their own scope.)
function MultiselectDirective($http, $sce) {
return {
restrict: 'E',
replace: true,
templateUrl: 'temp.html',
scope: {
filter: "=",
name: "#",
url: "#"
},
link: function(scope, element, attrs){
//do stuff
scope.function_i_need_to_call = function(){
//updates directtive template styles
}
}
}
}
The best solution and the angular way - use event.
Live example on jsfiddle.
angular.module('ExampleApp', [])
.controller('ExampleOneController', function($scope) {
$scope.raise = function(val){
$scope.$broadcast('raise.event',val);
};
})
.controller('ExampleTwoController', function($scope) {
$scope.raise = function(val){
$scope.$broadcast('raise.event',val);
};
})
.directive('simple', function() {
return {
restrict: 'A',
scope: {
},
link: function(scope) {
scope.$on('raise.event',function(event,val){
console.log('i`m from '+val);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="ExampleApp">
<div ng-controller="ExampleOneController">
<h3>
ExampleOneController
</h3>
<form name="ExampleForm" id="ExampleForm">
<button ng-click="raise(1)" simple>
Raise 1
</button>
</form>
</div>
<div ng-controller="ExampleTwoController">
<h3>
ExampleTwoController
</h3>
<form name="ExampleForm" id="ExampleForm">
<button ng-click="raise(2)" simple>
Raise 2
</button>
</form>
</div>
</body>
I think better solution to link from controller to directives is this one:
// in directive
return {
scope: {
controller: "=",
},
controller: function($scope){
$scope.mode = $scope.controller.mode;
$scope.controller.function_i_need_to_call = function(){}
$scope.controller.currentState = state;
}
}
// in controller
function testCtrl($scope){
// config directive
$scope.multiselectDirectiveController = {
mode: 'test',
};
// call directive methods
$scope.multiselectDirectiveController.function_i_need_to_call();
// get directive property
$scope.multiselectDirectiveController.currentState;
}
// in template
<Multiselect-directive controller="multiselectDirectiveController"></Multiselect-directive>

Confuse about the scope inherit between directive in AngularJS

I am wondering how to implement the scope inherit between directives.
For example:
<html ng-app="app">
<head>
<title>TEST DRAG</title>
</head>
<body ng-controller="main">
<dragcont>
<dragitem></dragitem>
</dragcont>
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script type="text/javascript">
(function(){
var app = angular.module("app", []);
app.controller("main", function($scope){
$scope.name = "Hello";
})
.directive("dragcont", function(){
return {
restrict: "AE",
scope: {
},
controller: function($scope){
$scope.name = "dragcont";
},
link: function(scope, EL, attrs){
}
}
})
.directive("dragitem", function(){
return {
restrict: "AE",
controller: function($scope){
console.log($scope.name);
},
link: function(scope, EL, attrs){
}
}
})
})()
</script>
</body>
</html>
When I run this, it always prints Hello. It seems that dragitem can inherit the scope from main controller, but what if I want it to inherit from dragcont?
Isolate scope is used to "isolate" the inner workings of a directive from its usage. As such, the scope neither inherits from its parent, nor can be inherited from by the child directives and expressions.
So, for the isolate foo directive:
.directive("foo", function(){
return {
scope: {},
link: function(scope){
scope.inner = "hidden from outside";
}
}
})
the child directives and expression will not inherit its isolate scope.
<foo>
<span>{{inner}} will be undefined</span>
</foo>
Using a template:
On the other hand, a template of a directive foo is known to the author of the directive, and so it does use the isolate scope. The following would have worked, if foo had a template:
scope: {},
template: '<span>{{inner}}</span>',
link: function(scope){
scope.inner = "hidden from outside";
}
Using manual "transclusion":
Occasionally, it makes sense to allow the user of the directive to specify a custom template. The author of the directive may also want to expose special "magic" variables to use in the custom template, not unlike $index, $first, etc.. of ng-repeat.
This can be done with a manual transclusion:
scope: {},
transclude: true,
template: '<div>{{header}}</div>\
<placeholder></placeholder>',
link: function(scope, element, attrs, ctrls, transclude){
scope.header = "I am foo"; // still only visible in the template
// create a new scope, that inherits from parent, but a child of isolate scope
var anotherScope = scope.$parent.$new(false, scope);
anotherScope.$magic = "magic";
// transclude/link against anotherScope
transclude(anotherScope, function(clonedContents){
element.find("placeholder").replaceWith(clonedContents);
}
}
Now, you can have access to $magic variable inside the transcluded contents and to the outer scope (assuming it has $scope.name = "John")
<foo>
<div>I can see {{name}} and {{$magic}}</div>
</foo>
The resulting DOM will be:
<foo>
<div>I am foo</div>
<div>I can see John and magic</div>
</foo>
It looks like you are still missing some work to be able to make a directive inherit from another.
I think this code will help you:
http://codepen.io/anon/pen/EaPNqp?editors=101
Also, you might want to read:
http://david-barreto.com/directive-inheritance-in-angularjs/
CODE:
var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
$scope.data1 = "1";
$scope.data2 = "2";
})​var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
$scope.data1 = "1";
$scope.data2 = "2";
})
.directive('myWrapper', function() {
return {
restrict: 'E'
, transclude: true
, scope: true
, template: '<h1>{{ title }}</h1><ng-transclude></ng- transclude><h2>Finished wrapping</h2>'
, controller: function($scope, $element, $attrs){
$scope.title = $attrs.title;
$scope.passdown = $attrs.passdown;
}
};
})
.directive('myInner1', function() {
return {
restrict: 'E'
, require: 'myWrapper'
, template: 'The data passed down to me is {{ passdown }}'
};
})
.directive('myInner2', function() {
return {
restrict: 'E'
, require: 'myWrapper'
, template: 'The data passed down to me is {{ passdown }}'
};
});
.directive('myWrapper', function() {
return {
restrict: 'E'
, transclude: true
, scope: true
, template: '<h1>{{ title }}</h1><ng-transclude></ng- transclude><h2>Finished wrapping</h2>'
, controller: function($scope, $element, $attrs){
$scope.title = $attrs.title;
$scope.passdown = $attrs.passdown;
}
};
})
.directive('myInner1', function() {
return {
restrict: 'E'
, require: 'myWrapper'
, template: 'The data passed down to me is {{ passdown }}'
};
})
.directive('myInner2', function() {
return {
restrict: 'E'
, require: 'myWrapper'
, template: 'The data passed down to me is {{ passdown }}'
};
});
which is found very useful. Make sure you read the comments below the article as well.
Pay attention to the "require" property.
Regards.

Update directive scope when attribute changes using controllerAs syntax

What I need is to inject the data inside a directive dependent on the value passed to this directive via HTML element attribute value, which changes dynamically.
Here's my code:
angular.module('test', [])
.controller('ctrl', function (dataService) {
var vm1 = this;
vm1.data = dataService;
vm1.change = function () {
vm1.data.testValue = !vm1.data.testValue;
}
})
.directive('myDrct', function () {
return{
restrict: 'E',
controller: 'drctCtrl',
controllerAs: 'vm',
scope:{
passedValue: '#pass'
},
template: 'Actual value in directive: {{vm.passedValue}}'
}
})
.controller('drctCtrl', function ($scope) {
var vm = this;
vm.passedValue = $scope.passedValue;
$scope.$watch('watcher', function () {
vm.passedValue = $scope.passedValue;
})
})
.factory('dataService', function () {
return{
testValue: true
}
});
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl as vm">
Value passed as a parameter to directive: {{vm.data.testValue}}
<button ng-click="vm.change()">change</button>
<div>
<my-drct pass="{{vm.data.testValue}}"></my-drct>
</div>
</div>
And here is a demo plunker:
http://plnkr.co/edit/aAqKaD4G7QdwBYCLWAvM?p=preview
As suggested in many similar topics on StackOverflow, I've tried $scope.$watch, but I must be doing something wrong. The value gets inserted to the directive once and it doesn't get updated when the attribute value changes.
What shoud I change in the watcher function to get it to work? Or maybe this approach is not good at all in my case and I should try something else?
EDIT:
I must use "controllerAs vm" syntax, since this code is just a part of a bigger app already dependent on it.
The only thing you needs to change in your code in controller: vm.scope = $scope; instead of vm.passedValue = $scope.passedValue; and in a template change {{vm.passedValue}} to {{vm.scope.passedValue}}
Plunker: http://plnkr.co/edit/q8JBFu4FD5ECPFHYlPg6?p=preview
angular.module('test', [])
.controller('ctrl', function (dataService) {
var vm1 = this;
vm1.data = dataService;
vm1.change = function () {
vm1.data.testValue = !vm1.data.testValue;
}
})
.directive('myDrct', function () {
return{
restrict: 'E',
controller: 'drctCtrl',
controllerAs: 'vm',
scope:{
passedValue: '#pass'
},
template: 'Actual value in directive: {{vm.scope.passedValue}}'
}
})
.controller('drctCtrl', function ($scope) {
var vm = this;
vm.scope = $scope;
})
.factory('dataService', function () {
return{
testValue: true
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl as vm">
Value passed as a parameter to directive: {{vm.data.testValue}}
<button ng-click="vm.change()">change</button>
<div>
<my-drct pass="{{vm.data.testValue}}"></my-drct>
</div>
</div>

AngularJS How: Scope hierarchy for Directive

Directive in AngularJS: I find out that the elements inside an element with the directive do not inherit its "scope".
For example:
app
.controller('xxx', function($scope) {})
.directive('yyy', function() {
return {
scope: {},
link: function(scope,elem,attrs) {}
};
});
When we use it in the HTML:
<body ng-controller="xxx">
<div id='withD' yyy>
<div id='inside'>Inside the element with a directive</div>
</div>
</body>
"body" will have a scope whose $id may be 003;
then "#withD" will have an isolate scope $id=004;
the "#inside" will have the scope $id=003, which means the "#inside" inherits "body"'s scope.
If I use "transinclude" for the directive "yyy"; then "body" scope.$id=003, "#withD" scope.$id=004, "#inside" scope.$id=005; moreover, 003 has two children 004 and 005. However, I wanna make the element with the directive has an isolate scope and its child elements inherit the scope.
I read over "ui.bootstrap.tabs" source code but I do not like the style, for it is strange and also not make the parent element share its scope with child elements'; it looks like this:
app
.directive('xitem', function() {
scope: {},
controller: function($scope) {
$scope.subitem = [];
return {
add: function(xsubitem) {$scope.subitem.push(xsubitem);}
}
},
link: function(scope,elem,attrs) {}
})
.directive('xsubitem', function() {
require: '^xitem',
link: function(scope,elem,attrs,ctrl) {ctrl.add(elem);}
});
My expectation is that:
<div ng-controller="xxx">
<div yyy>
<button ng-click="sayHi()">Hi</button>
<div>
</div>
when you click the "Hi" button, the alert dialog will pop up with the message "Hello World" not "Error: Scope".
app
.controller('xxx', function($scope) {
$scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
return {
scope: {},
link: function(scope,elem,attrs) {
scope.sayHi = function(){alert('Hello World');};
}
};
});
Moreover, I tried this:
app
.controller('xxx', function($scope) {
$scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
return {
scope: {},
controller: function($scope, $compile) {$scope._compile = $compile;}
link: function(scope,elem,attrs) {
elem.children().forEach(function(one) {
scope._compile(one)(scope);
});
scope.sayHi = function(){alert('Hello World');};
}
};
});
Then it will pop up two alert dialogs with the message "Error: Scope" and "Hello World" respectively.
Now I found the solution - load template dynamically and use $compile to specify scope:
.controller('main', function($scope) {
$scope.sayHi = function() {alert('scope error');};}
)
.directive('scopeInherit', ['$http', '$compile', function($http, $compile) {
return {
scope: {},
link: function(scope, elem, attrs) {
scope.sayHi = function() {alert('hello world');};
scope.contents = angular.element('<div>');
$http.get(elem.attr('contentsURL'))
.success(function (contents) {
scope.contents.html(contents);
$compile(scope.contents)(scope);
});
},
};
}]);
Then we write HTML:
<div ng-controller="main">
<div scope-inherit contents="test.html"></div>
</div>
where there is a test.html:
<button ng-click="sayHi()">speak</button>
Then click on the "speak" button, it will pop up the alert dialog with "hello world"
To do what you want, you need to use a template (either as a string or a templateUrl). If angularjs would work how you expect it in this case then a lot of the angular directives wouldn't work right (such as ng-show, ng-click, etc).
So to work how you want it, change your html to this:
<script type="text/ng-template" id="zzz.html">
<button ng-click="sayHi()">Hi 2</button>
</script>
<div ng-controller="xxx">
<button ng-click="sayHi()">Hi 1</button>
<div yyy></div>
</div>
And update your directive definition to use a templateUrl (or you can provide the string as a template property)
app
.controller('xxx', function($scope) {
$scope.sayHi = function() {
console.error('Error: Scope in xxx', new Date());
};
})
.directive('yyy', function() {
return {
scope: {},
templateUrl: 'zzz.html',
link: function(scope, elem, attrs) {
scope.sayHi = function() {
console.log('Hello World in zzz', new Date());
};
}
};
});
Here's a plunker with this code: http://plnkr.co/edit/nDathkanbULyHHzuI2Rf?p=preview
Update to use multiple templates
Your latest comment was a question about what if you wanted to use different templates on the same page. In that case we can use ng-include.
html:
<div yyy contents="template1.html"></div>
<div yyy contents="template2.html"></div>
<div yyy contents="template3.html"></div>
js:
app
.controller('xxx', ...)
.directive('yyy', function() {
return {
scope: {
theTemplateUrl: '#contents'
},
template: '<ng-include src="theTemplateUrl"></ng-include>',
link: function(scope, elem, attrs) {
scope.sayHi = function() {
console.log('Hello World in yyy', new Date());
};
}
};
});
The benefit of using ng-include is that this is already built into angularjs and is well tested. Plus it supports loading template either inline in a script tag or from an actual url or even pre-loaded into the angular module cache.
And again, here is a plunker with a working sample: http://plnkr.co/edit/uaC4Vcs3IgirChSOrfSL?p=preview

Categories

Resources