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
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>
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>
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.
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>
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