How to access controller of a child directive - angularjs

If I have a directive FilePicker that contains in its template an instance of the Modal directive, how can I get a reference to an instance of the Modal directive's controller from within the FilePicker's controller?
I ask, of course, because I cannot find any way to do this, but this is a bitter disappointment in light of everything I've heard about directive controllers being the cornerstone of directive-to-directive communication rather than doing everything via $scope.

An instance of something in template? What? A template is just a simple string and that's all.
About controllers:
DOM elements can be managed by angular controllers. Directives are being applied to DOM elements. Then can use controllers too. You can simply describe a directive controller by:
// directive with name parentDirective
{
link: function () { ... },
restrict: 'A',
controller: [ '$scope', function ($scope) {
this.sayHello = function () { alert('hello'); }
// 'this' references the instance of the directive controller and then can be required by a child
}],
template: '<div><child-directive/></div>'
}
// child directive with the name childDirective
{
require: '^parentDirective',
link: function (scope, $element, attributes, parentDirectiveController) {
parentDirectiveController.sayHello();
}
}

Related

Dynamically update a directive attribute using vanilla JavaScript

I have an Angular JS directive that looks something like this:
function ($sce) {
'use strict';
return {
restrict: 'E',
templateUrl: $sce.trustAsResourceUrl('...'),
scope: {
serviceName: '#?',
},
controller: 'MyController',
link: function () {}
};
}
The directive is instantiated like so:
<my-directive service="My Cool Service"></my-directive>
Recently, some consumers of this directive would like the ability to modify the service attribute after the directive has been instantiated and have the directive reflect the change. Here is an example of what a specific consumer is doing:
const directive = document.querySelector('my-directive');
directive.setAttribute('service', 'Another Service Name');
This makes sense; however, the directive does not reflect the change once they set the attribute. I am figuring out a way to accomplish this. I have tried using scope.$watch and $observe to no avail; example:
link: function (_, _, attrs) {
attrs.$observe("serviceName", newValue => updateServiceName(newValue));
}
Any insights on how to accomplish this? Thanks!
The serviceName property inside your directive's scope definition should be bound with = instead of #. Then, from the outer code, just pass in the service name with a variable.
$scope.myServiceName = "My Cool Service";
<my-directive service="{{myServiceName}}"></my-directive>
Now if you change the myServiceName value, the change will be reflected in the child directive.

Communication between directive function and transcluded controller

I have a directive that "manages" all the instances of the associated component. I would like the directive function to be able to call a function in the transcluded controller, and vice versa. An isolate scope in the link function does not provide this capability. Is this even possible in Angular 1.x? I have everything working, but only by using $broadcast() and hard-coding names into all the child controllers. It feels like a hack.
The directive looks like this:
app.directive('myDirective', function () {
return {
transclude: true,
template: '...<ng-transclude />...',
link: function (scope, elem, attrs) {
...
}
};
});
And the transcluded content looks like this:
<div ng-controller="MyController">
...
</div>
Ideally, I would like to be able to "inject" an object from the directive function into the transcluded controller that contains data and callbacks.

Directive scope, calling controller functions using controllerAs and getting access to controller variables

I have a directive that loads a template and compiles it:
function question($templateRequest, $compile) {
return {
restrict: 'E',
scope: {
apply: '&apply',
item: '=item'
},
bindToController: true,
link: function (scope, element, attrs) {
//debugger;
var templateUrl = 'someRandomUrl'
$templateRequest(templateUrl).then(function (template) {
$compile(element.html(template).contents())(scope.$parent);
}, function () {
// An error has occurred
$compile(element.html("error").contents())(scope);
});
}
}
}
The template is correctly generated, I can also call functions from the template html to myController like:
ng-click='vm.apply()'
On myController I am using controllerAs vm syntax and when calling normal functions from the controller view I can access the vm variable inside the scope. Functions called from the template don't have access to the vm variable inside the body.
Template is generated under myController view.
I also have tried using different options for directive configuration, as in removing isolated scope, removing bindToController, etc.
Is it possible? How?
(I know I can add it as an extra argument but wanted to avoid that)
Thanks

What is the correct way to access controller that was required inside directive controller?

I have a directive with require property:
require: '^testBox'
now I want to get testBox controller inside controller of my directive. How should I do it?
I was trying to do so:
controller: function(){
this.testBox.user
}
but looks like it does not work.
It's clear for me how to get required controller inside link function. But is there a way to get it inside controller without using link?
Code on plunker.
This is still an open issue. So at the moment you can not just inject the required controller into your directive controller. I have updated your Plunker. It's definitely a bit hacky but the problem is; You cannot expose the TextBoxCtrl to the UserCtrl in either the pre or post link function because the controller gets executed first. So my idea is to use a watcher to observe a scope varibale called textBox. Once the value is defined I declare a variable on the UserCtrl and remove the watcher. Now you can simply use it in your template like so:
{{ user.textBox.name }}
Here is the code for the link function and the controller of the user directive:
link: function($scope, $element, $attrs, ctrl) {
$scope.textBox = ctrl
},
controller: function($scope) {
var vm = this;
var watcher = $scope.$watch('textBox', function(newVal) {
if(newVal) {
vm.textBox = newVal;
watcher();
}
});
}
However, you can also go with a link function instead. The required controller will be injected as the fourth parameter.
When you use controllerAs it's just added as a property of the underlying scope object (using the name you've defined). Knowing this, you can attach the parent controller instance as a property of your child controller instance as follows:
function exampleDirective() {
return {
require: '^testBox',
link: function (scope, element, attrs, testBox) {
scope.example.testBox = testBox;
},
controllerAs: 'example',
controller: function() {
// silly example, but you get the idea!
this.user = this.testBox.user;
}
}
};

AngularJS - exposing controller api to directive

How do I make controller functions visible to a directive? Do I attach the methods to the scope, and inject the scope into the directive? Is it a good idea in the first place? I want to manipulate model data from within the UI.
It really dependes on what you want to do.
Since you want to access the controller's scope from the directive, I suggest you declare your directive with it's scope shared with the parent controller by setting it's scope prop to false:
app.directive('directiveName', function() {
scope: false,
link: function(scope) {
// access foo from the controler's scope
scope.foo;
}
});
This is a nice example with how directives can be hooked up to a controller
http://jsfiddle.net/simpulton/GeAAB/
DIRECTIVE
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
HOOKUP IN CONTROLLER
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
VIEW
<my-component ng-model="message"></my-component>
Adding to #grion_13 answer, scope:true would also work since it creates a new scope that is child of the parent scope so has access to parent scope data.
But a true reusable directive is one which get it input data using isolated scope. This way as long as your html+ controller can provide the right arguments to the directive isolated scope, you can use the directive in any view.

Resources