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.
Related
I understand how to pass directives through my custom directive, like this:
Page.html
<my-directive read-only-attr="myVariable" label-style-attr="anotherVariable"></my-directive>
Directive
myApp.directive("myDirective", function () {
return {
restrict: "E",
templateUrl: "myTemplate.html",
scope: {
readOnlyScopeVar: "=readOnlyAttr",
styleScopeVar: "=labelStyleAttr"
},
link: function (scope, element, attrs) {
}
};
});
Template
<div>
<label ng-style="styleScopeVar" />
<input type="text" ng-readonly="readOnlyScopeVar" />
</div>
My template is much more complex than this but I simplified it for the question.
My question is: How do I prevent ngReadonly and ngStyle from having to run if the user hasn't specified a "read-only-attr" or "label-style-attr" on my directive? There are tons of common angular directives that I want to allow people to apply to the input and other elements inside my template (ngClass, ngDisabled, ngChange, ngPattern, ngIf, etc), but I don't want to run them all if the person hasn't specified them on my directive. It's as if I need a template to build the template.
Also, note that I've read about transclusion but I don't like the idea of allowing the user to edit the input element directly, and there are multiple elements I may want to apply things to like in this example I could change the label color if the read-only-attr reference is true.
One way to do it would be to use $compile. Here's a working plnkr:
https://plnkr.co/edit/S8pUSH?p=preview
Note there are many ways to do this, and this one is just one simple example for demonstration:
var app = angular.module('app', []); //define the module
//setup the template
app.run(['$templateCache', function($templateCache){
$templateCache.put('someDirectiveTmpl','<div>\
<label $$%%ngStylePlaceholder$$%% />My Label:</label>\
<input type="text" $$%%ngReadonlyPlaceholder$$%% />\
</div>');
}])
/**
* #description someDirective gets a config object that holds dynamic directives' names and values. e.g.:
* {
* 'ngStyle': '{background: red;}',
* 'ngReadonly': true
* }
*
*/
app.directive('someDirective', ['$log', '$compile', '$templateCache', function($log, $compile, $templateCache){
return {
restrict: 'AE',
controllerAs: 'someDirectiveCtrl',
scope: {},
bindToController: {
directiveConfig: '='
},
controller: function($scope, $element){
// a method to convert camelcase to dash
function camelCaseToDash( myStr ) {
return myStr.replace( /([a-z])([A-Z])/g, '$1-$2' ).toLowerCase();
}
// get the template
var template = $templateCache.get('someDirectiveTmpl');
var placeHolderRegExp = /\$\$%%(.*)\$\$%%/g;
// place the binding per existing property
angular.forEach(this.directiveConfig, function(varName, key){
template = template.replace('$$%%' + key + 'Placeholder$$%%', camelCaseToDash(key) + '="someDirectiveCtrl.directiveConfig.' + key + '"');
});
// remove unneeded properties placeholders
template.replace(placeHolderRegExp, '');
//compile the directive
var templateElement = angular.element(template);
$compile(templateElement)($scope);
// append to element
$element.append(templateElement);
}
}
}]);
Note the $$%%ngAnythingPlaceholder$$%%in the template. I get the config from parent directive (in plnkr I've used a controller for simplicity). I use a config object in the example (you could do this with separate variables, but I like setting a config object API).
I then replace the placeholders according to what I've got in the config and remove what I don't need. I then compile the template.
In the parent directive's controller you could do something like what I did in the controller:
$scope.config = {
ngReadonly: true
}
Again, I note you should not use a controller and of course, not to
use the $scope itself but the directive's controller's this. I use
$scope and controller here only for ease of demo.
You can add anything you want to this config (and of course, add placeholders to the various parameters in the template).
Now just add the directive to your template:
<some-directive directive-config="config"></some-directive>
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;
}
}
};
The question title explain my problem i want to send data from a controller to directive so i can use the data in the directive controller or view.
Here is the controller code:
$scope.following = product.vendorId.isUserFollowing;
In the controller view:
<vas-follow following="{{following}}"></vas-follow>
following the property am trying to pass to the directive, the directive code:
.directive('vasFollow', vasFollow);
function vasFollow() {
var directive = {
restrict: "EA",
scope: {
following: '#'
},
link: link,
controller: vasFollowCtrl,
templateUrl: 'templates/directives/vasFollow.html',
};
return directive;
function link(scope, element, attrs) {
/* */
};
}
I tried first to use the following like so {{following}} in the directive view but it's not passing, also it is undefined in the directive controller.
I have read a lot of slimier issues but, i couldn't conclude why am having this problem.
Use ng-model for directive instead of your own replacement for it
Remove {{}} from assignment to share link to variable instead of just evaluated value
And please, use div or common DOM element instead of exact naming directive - it have side-effects in I.E.
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();
}
}
Im wondering if there is a way to pass an argument to a directive?
What I want to do is append a directive from the controller like this:
$scope.title = "title";
$scope.title2 = "title2";
angular.element(document.getElementById('wrapper')).append('<directive_name></directive_name>');
Is it possible to pass an argument at the same time so the content of my directive template could be linked to one scope or another?
here is the directive:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
replace:true
};
})
What if I want to use the same directive but with $scope.title2?
You can pass arguments to your custom directive as you do with the builtin Angular-directives - by specifying an attribute on the directive-element:
angular.element(document.getElementById('wrapper'))
.append('<directive-name title="title2"></directive-name>');
What you need to do is define the scope (including the argument(s)/parameter(s)) in the factory function of your directive. In below example the directive takes a title-parameter. You can then use it, for example in the template, using the regular Angular-way: {{title}}
app.directive('directiveName', function(){
return {
restrict:'E',
scope: {
title: '#'
},
template:'<div class="title"><h2>{{title}}</h2></div>'
};
});
Depending on how/what you want to bind, you have different options:
= is two-way binding
# simply reads the value (one-way binding)
& is used to bind functions
In some cases you may want use an "external" name which differs from the "internal" name. With external I mean the attribute name on the directive-element and with internal I mean the name of the variable which is used within the directive's scope.
For example if we look at above directive, you might not want to specify another, additional attribute for the title, even though you internally want to work with a title-property. Instead you want to use your directive as follows:
<directive-name="title2"></directive-name>
This can be achieved by specifying a name behind the above mentioned option in the scope definition:
scope: {
title: '#directiveName'
}
Please also note following things:
The HTML5-specification says that custom attributes (this is basically what is all over the place in Angular applications) should be prefixed with data-. Angular supports this by stripping the data--prefix from any attributes. So in above example you could specify the attribute on the element (data-title="title2") and internally everything would be the same.
Attributes on elements are always in the form of <div data-my-attribute="..." /> while in code (e.g. properties on scope object) they are in the form of myAttribute. I lost lots of time before I realized this.
For another approach to exchanging/sharing data between different Angular components (controllers, directives), you might want to have a look at services or directive controllers.
You can find more information on the Angular homepage (directives)
Here is how I solved my problem:
Directive
app.directive("directive_name", function(){
return {
restrict: 'E',
transclude: true,
template: function(elem, attr){
return '<div><h2>{{'+attr.scope+'}}</h2></div>';
},
replace: true
};
})
Controller
$scope.building = function(data){
var chart = angular.element(document.createElement('directive_name'));
chart.attr('scope', data);
$compile(chart)($scope);
angular.element(document.getElementById('wrapper')).append(chart);
}
I now can use different scopes through the same directive and append them dynamically.
You can try like below:
app.directive("directive_name", function(){
return {
restrict:'E',
transclude:true,
template:'<div class="title"><h2>{{title}}</h3></div>',
scope:{
accept:"="
},
replace:true
};
})
it sets up a two-way binding between the value of the 'accept' attribute and the parent scope.
And also you can set two way data binding with property: '='
For example, if you want both key and value bound to the local scope you would do:
scope:{
key:'=',
value:'='
},
For more info,
https://docs.angularjs.org/guide/directive
So, if you want to pass an argument from controller to directive, then refer this below fiddle
http://jsfiddle.net/jaimem/y85Ft/7/
Hope it helps..
Controller code
myApp.controller('mainController', ['$scope', '$log', function($scope, $log) {
$scope.person = {
name:"sangeetha PH",
address:"first Block"
}
}]);
Directive Code
myApp.directive('searchResult',function(){
return{
restrict:'AECM',
templateUrl:'directives/search.html',
replace: true,
scope:{
personName:"#",
personAddress:"#"
}
}
});
USAGE
File :directives/search.html
content:
<h1>{{personName}} </h1>
<h2>{{personAddress}}</h2>
the File where we use directive
<search-result person-name="{{person.name}}" person-address="{{person.address}}"></search-result>
<button my-directive="push">Push to Go</button>
app.directive("myDirective", function() {
return {
restrict : "A",
link: function(scope, elm, attrs) {
elm.bind('click', function(event) {
alert("You pressed button: " + event.target.getAttribute('my-directive'));
});
}
};
});
here is what I did
I'm using directive as html attribute and I passed parameter as following in my HTML file. my-directive="push" And from the directive I retrieved it from the Mouse-click event object. event.target.getAttribute('my-directive').
Insert the var msg in the click event with scope.$apply to make the changes to the confirm, based on your controller changes to the variables shown in ng-confirm-click therein.
<button type="button" class="btn" ng-confirm-click="You are about to send {{quantity}} of {{thing}} selected? Confirm with OK" confirmed-click="youraction(id)" aria-describedby="passwordHelpBlock">Send</button>
app.directive('ngConfirmClick', [
function() {
return {
link: function(scope, element, attr) {
var clickAction = attr.confirmedClick;
element.on('click', function(event) {
var msg = attr.ngConfirmClick || "Are you sure? Click OK to confirm.";
if (window.confirm(msg)) {
scope.$apply(clickAction)
}
});
}
};
}
])