Directive Inside another directive. Controller can't watch - javascript

I have 2 simple directives...
the parent directive:
.directive('modal', [function () {
return {
replace: true,
scope: {
/* attributes */
},
templateUrl: 'modal.tpl.html',
transclude: true,
link: function (scope) {
/* code */
}
};
the child directive
.directive('keypad', function () {
'use strict';
return {
templateUrl: 'keypad.tpl.html',
scope: {
value: '=',
},
link: function (scope, element, attrs) {
/* code */
}
};
and finally the controller:
.controller('ctrl', ['$scope', '$timeout', function ($scope, $timeout) {
$scope.$watch('howMuch', function(){
console.log('wont work ;-(');
});
}
and my template looks like this:
<div modal>
<div keypad class="keypad" value="howMuch"></div>
</div>
Any idea why the child directive can't change the howMuch value on the controller?
The same code but WITHOUT the parent directive works PERFECT.

The parent directive has an isolate scope. You didn't show the scope attributes, but howMuch needs to be accessed via a property in your isolated scope in order to update the value in the controller's scope.

Related

How to get controller scope from multi-level directives in AngularJS(without $parent)

How can I access controller scope from multi-level directives in below structure:
I create a directive that has multi-level scopes inside its.
1. Controller scope
1.2. Directive1 scope(main directive)
1.2.1. Directive2 scope
1.2.1.1 Directive3 scope
I want to get the controller scope from directive 3.
please don't refer to $parent because the parent level it's not certain and a user may use this directive inside another directive.(see below codes)
<div ng-controller="Test">
<custom-dir>
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
<custom-dir>
</div>
The users create a function in the Test controller and the function will be called in my Directive 3 scope(how to get controller scope?).
<div ng-controller="Test">
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
</div>
More details(please don't refer to syntax error):
The controller is:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal, $http, $filter, $cookieStore, Common, $cookies) {
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
the dir1 directive:
App.directive("dir1", function ($compile, $filter, $rootScope, $timeout, Common, $window, $http) {
return {
restrict: "E",
priority: 1,
terminal: false,
templateUrl: "Content/html/Table.html?version=2.6",
scope: {
model: "=",
columns: "=",
config: "=",
search: "#",
},
link: function (scope, elem, attrs) {
scope.CallFunc = function (html) {
if (typeof (html) =="function" )
return html();
else {
return scope.$parent.$eval(html + "()", {});
}
}
}
}
});
the dynamic directive compile the runTest output
App.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
If I change the line $compile(ele.contents())(scope); to $compile(ele.contents())(scope.$parent.$parent); it's work.
In this directive, I need get the controller scope without $parent because
some users may use this directive inside another directive same below:
<custom-dir>
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
</custom-dir>
The using HTML tag
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
This issue handle with following codes:
A service for storing the controller scope:
App.service('TableService', function () {
return {
MyScope: null
};
});
Inject the TableService to dynamic directive(this directive compiles dynamic content):
App.directive('dynamic', function ($compile,TableService) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(TableService.MyScope);
});
}
};
});
And finally in the controller:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal,
$http, $filter, $cookieStore, Common, $cookies,TableService) {
TableService.myScope = $scope;
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
After that, the dynamic directive can access controller scope and all dynamic events(like testHtml) will be called even if the directive put in another directive(without using the $parent).
thank you #shaunhusain, huan feng for giving me an idea.
In child controller do something like:
$scope.$broadcast('yourEvent');
In parent controller do the listener:
$scope.$on('yourEvent' , function(){
//Handle your logic
});
A special case service
.service('DirectDispatcher', function(){
return {
fireMe: angular.noop
}
});
First directive registers a function callback
.directive(
...
link:function(DirectDispatcher){
function myHandler() {window.alert('just testing')}
DirectDispatcher.fireMe = myHandler;
}
...
);
Second directive fires the function callback
.directive(
...
link:function(DirectDispatcher){
DirectDispatcher.fireMe();
}
...
);

Cannot pass boolean to directive with Angular

I'm trying to pass a boolean value from my controller into my isolated scope directive. When I console.log(attrs) from the directive's link function, the someBoolean attribute is a string, rendering the actual text "main.bool" instead of a true or false value. When I toggle the boolean value from the outer controller, I want it to be updated in the directive.
https://plnkr.co/edit/80cvLKhFvljnFL6g7fg9?p=preview
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
someBoolean: '='
},
templateUrl: 'myDirective.html',
link: function(scope, element, attrs) {
console.log(scope);
console.log(attrs);
},
controller: function($scope, $element, $attrs) {
console.log(this);
},
controllerAs: 'directiveCtrl',
bindToController: true
};
});
Controller
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.bool = true;
vm.change = function() {
vm.bool = !vm.bool;
}
});
The template
<div>
Inside directive: {{someBoolean}}
</div>
As you have attached your directive Controller to directiveCtrl instead of mainCtrl, you'll access the variable someBoolean using directiveCtrl.someBoolean.
In this case, change the HTML to:
<div>
Inside directive: {{directiveCtrl.someBoolean}}
</div>
Plunker.
Another solution would be to remove the bindToController property inside your directive. With this, you don't need to use the controller name before the variable. Working Plunker.
Read more about this bindToController feature here.

Handling click on transcluded content of an angularjs directive

So lets say I have a directive that looks like so:
(function () {
'use strict';
angular
.module('app')
.directive('foo', foo);
/* #ngInject */
function foo() {
var directive = {
restrict : 'E',
controller: controller,
controllerAs: 'vm',
link: link,
transclude : true,
replace : true,
bindToController: true,
scope: {},
template : '<div><div ng-transclude></div>'
};
return directive;
function controller() {
var vm = this;
vm.click = function () {
// do something, add class whatever
}
}
function link(scope, elem, attrs) {}
}
}());
Now if I use the directive like so:
<foo>
<button ng-click="vm.click()">Click me!</button>
</foo>
I wanted to access the click function of the directive, how do I do that?
The transcluded contents scope is not created as a child of it's directives scope if there directive itself is defined with an isolated scope.
See:
Why ng-transclude's scope is not a child of its directive's scope - if the directive has an isolated scope?
As such kwan245's answer is correct for your particular example.
remove scope: {} from directive declaration and it should works.

How to specify which $scope a directive uses?

I have a directive that is watching something on $scope, and it is getting the wrong $scope.
so I have a state setup that specifies a controller:
.state('app.mystate', {
url: 'search/:searchText',
views: {
'mainPane#': {
templateUrl: 'views/content/search.html',
controller: 'ABCController'
}
},
resolve: {
searchPromise: ['$http', '$stateParams', function ($http, $stateParams) {
console.log($stateParams.searchText);
return $http.get(...blah...blah).then(blahblah);
}]
}
})
When this state gets activated, it goes to this view:
<ul ...>
<div ng-repeat="...">
<li ...>
<div ng-include="'views/widgets/somewidget.html'"></div>
</li>
</div>
</ul>
the ng-include loads this, which has a specific controller specified. And a directive.
<div ng-controller="XYZController">
<my-chart chart="chart" ...></my-chart>
</div>
Here is my directive:
angular.module('app.directive')
.directive('myChart', function () {
return {
template: '<div></div>',
scope: {
chart: '='
},
// wrong scope!
scope.$watch('chart', function (chart) { }
I figured that the <div> that contains the my-chart directive, that directive would get the $scope that goes into XYZController.
But the directive is getting the scope that is injected into ABCController, not XYZController as I want/expected. I can see XYZContoller getting activated.
How do I get the $scope that is injected into XYZController be the same scope that the my-chart directive sees?
The sample code for you directive seems to have been truncated/broken during copy/paste so it's hard to see where that scope.$watch call is at. If it is in the directive definition, after the return block, then it's never getting called but I would expect it to be in the controller.
Having said that, you can specify the controller to be used by the directive in the directive attributes which would seem appropriate here, unless you want to use the directive with different scopes..
<div>
<my-chart chart="chart"></my-chart>
</div>
angular.module('app.directive')
.directive('myChart', function () {
return {
controller: "XYZController",
bindToController: true
template: '<div></div>',
scope: {
chart: '='
}
})
.controller('XYZController', function($scope){
$scope.$watch('chart', function (chart) { });
});
scope: true will create a scope that is prototypically inerited from the parent scope, so you should be able to $watch a property on the parent scope.
angular.module('app.directive')
.directive('myChart', function () {
return {
template: '<div></div>',
scope: true,
link: function(scope){
scope.$watch('chart', function (chart) { });
});

Accessing parent controller in an Angular directive

I have directive that dynamically sets the header bar content for a given application state.
I would like to be able to access the functions and variables in the Controller of the current view, but I am only ever able to access my RootCtrl.
The directive looks like this.
return {
restrict: 'EA',
template: "<div ng-include='getState()'></div>",
transclude: true,
scope: false,
controller: ['$scope', '$state', function($scope, $state) {
//some logic to retrieve and return the correct header template html
}],
link: function(scope, element, attrs){
console.log(scope.test);
console.log(scope.test2);
}
}
And the controllers.
.controller('RootCtrl', function($scope, $state, $location, $rootScope) {
$scope.test = 'hello';
//...
})
.controller('ContactsCtrl', function($scope, $state, CustomerService) {
console.log('Contacts init');
$scope.test2 = 'hello 2';
//...
})
And when I navigate to the contacts state, the output looks like this.
hello
undefined
Contacts init
What should I do if I want to be able to access the test2 variable?
You will need to use the require property inside your directive.
This will make the scope of the defined controllers available inside the link function as 4th argument. You can access the scopes as an array inside the link function then.
Your code may look like:
return {
restrict: 'EA',
template: "<div ng-include='getState()'></div>",
transclude: true,
scope: false,
require:['^RootCtrl', '^ContactsCtrl'],
controller: ['$scope', '$state', function($scope, $state) {
//some logic to retrieve and return the correct header template html
}],
link: function(scope, element, attrs, requiredControllers){
console.log(requiredControllers[0].test);
console.log(requiredControllers[1].test2);
}
}
See the Angular documentation for Directives for some more examples (under the title Creating Directives that Communicate) and the explanation of the ^controller syntax.

Categories

Resources