I have an AngularJS directive. My directive is restricted to be used as an attribute. However, I want to get the value of the attribute. Currently, I have my attribute defined as:
angular.module('myDirectives.color', [])
.directive('setColor', function () {
return {
retrict: 'A',
link: {
pre: function () {
console.log('color is: ');
}
}
};
})
;
I use the attribute in the following manner:
<button type="button" set-color="blue">Blue</button>
<button type="button" set-color="yellow">Yellow</button>
I know that my directive is being used because I see 'color is: ' However, I can't figure out how to make it display 'blue' or 'yellow' at the end of that console statement. I want to get that value so I can do a conditional processing. How do I get the value of the attribute?
Thank you!
You can use the attributes argument of the link function:
angular.module('myDirectives.color', [])
.directive('setColor', function () {
return {
restrict: 'A',
link: {
pre: function (scope, element, attrs) {
console.log('color is: ' + attrs.setColor);
}
}
};
});
or you could create an isolate scope like this:
angular.module('myDirectives.color', [])
.directive('setColor', function () {
return {
restrict: 'A',
scope: {
setColor: '#'
},
link: {
pre: function (scope) {
console.log('color is: ' + scope.setColor);
}
}
};
});
Related
I have the following HTML / Angular code on a form:
<span class="error" ng-if="model.errors['message.email']" ng-bind="model.errors['message.email'][0]"></span>
I would like to use less code so the following would render the same:
<span class="error" model-validator="message.email" validator-errors="model.errors"></span>
Note
When validator-errors is not present then the default would be "errors" so I would get:
<span class="error" ng-if="errors['message.email']" ng-bind="errors['message.email'][0]"></span>
UPTATE 1
I tried the following:
.directive('modelValidator', function () {
return {
restrict: 'A',
replace: false,
link: function (scope, element, attrs) {
var validator = element.attr('model-validator');
if (validator === "null")
return;
var errors = element.attr('validator-errors');
element.attr('data-ng-if', errors + "['" + validator + "']");
element.attr('data-ng-bind', errors + "['" + validator + "'][0]");
}
};
But this is not adding the attributes ...
UPDATE 2
The directive is working. I would like to just add a few things:
How to use an attribute to specify which variable contains the errors?
On the directive "scope.model.errors" would be "scope.allErrorsToDisplay".
Do I need to watch all scope? Can I only watch model.errors?
Or considering (1), watch the variable in "validator-errors"?
Here is my current code:
angular.module('Application')
.directive('validator', function () {
return {
restrict: 'A',
replace: false,
link: function (scope, element, attribute) {
scope.$watch(function () {
if (scope.model.errors) {
if (scope.model.errors[attribute.validator]) {
element.show();
element.text(scope.model.errors[attribute.validator][0]);
} else
element.hide();
} else
element.hide();
});
}
}
});
Update 3
This seems to do everything I need.
Does anyone has any suggestion to improve it?
angular.module('app')
.directive('validator', ['$parse', function ($parse) {
return {
restrict: 'A',
replace: false,
link: function (scope, element, attributes) {
scope.$watch(function () {
var errors = $parse('validatorErrors' in attributes ? attributes["validatorErrors"] : 'model.errors')(scope);
if (errors) {
if (errors[attributes.validator]) {
element.show();
element.text(errors[attributes.validator][0]);
} else
element.hide();
} else
element.hide();
});
}
}
}]);
I think your approach is too complicated. The power of directives is that you don't have to add other directives to accomplish what you want. If i'm understanding your question correctly, you want your element to display a message if the error exists. How about this?
<!-- html -->
<div ng-controller="mainController">
<div validate="email"></div>
<div validate="name"></div>
</div>
// controller
function mainController ($scope) {
$scope.model = {
errors: {
email: 'Your email is invalid!'
, name: undefined
}
}
}
// directive
function validate () {
return {
restrict: 'A'
, replace: false
, link: function (scope, elem, attr) {
if (scope.model.errors[attr.validate]) {
elem.text(scope.model.errors[attr.validate]);
}
}
}
}
codepen
I've got following script
// Code goes here
angular.module('default', [])
.directive('webAbc', function($log) {
return {
restrict: 'A',
controller: function($scope, $element, $attrs, $transclude) {
this.checkboxes = [];
this.updateLinkedCheckboxes = function(value) {
angular.forEach(this.checkboxes, function(checkbox, index) {
checkbox.setChecked(value);
});
};
}
};
})
.directive('webDef', function($log) {
return {
restrict: 'C',
require: '^webAbc',
link: function (scope, iElement, iAttrs, webAbc, transcludeFn) {
iElement.bind('change', function () {
webAbc.updateLinkedCheckboxes(iElement.prop('checked'));
scope.$apply();
});
}
};
})
.directive('webGhi', function($log) {
return {
restict: 'A',
require: '^webAbc',
link: function (scope, iElement, iAttrs, webAbc, transcludeFn) {
scope.setChecked = function(value) {
$log.log('This element ' + iAttrs.name + ' cheked: ' + (!value ? 'checked' : ''));
$log.log(value);
if (value)
{
iElement.attr('checked', 'checked');
}
else
{
iElement.remoteAttr('checked');
}
};
webAbc.checkboxes.push(scope);
}
};
});
it should select or deselect all checkboxes in table in marked column, but I can't make it work with following solution.
First of all it seems, that only last webGhi is visible due to print out in console. And even more, it seems, that I can't uncheck checkbox for some reason.
Link to an example: http://jsbin.com/difihabe/1/
Thank you.
Use an isolated scope in the webGhi directive or all four instances of it will push the same scope (the parent):
.directive('webGhi', function($log) {
return {
restict: 'A',
require: '^webAbc',
scope: {},
link: ...
Also instead of adding/removing the checked attribute either use:
jQuery's prop() function: iElement.prop('checked', value);
Directly setting the DOM element's checked property:
iElement[0].checked = value;
Demo: http://jsbin.com/tudotugi/2/
http://jsfiddle.net/mato75/t48qn/
I have a directive, that if id is not passed, then it should generate one, but it looks like that the generation is to slow and the id is not present in the directive.
(function (angular) {
'use strict';
angular.module('Widgets.Module')
.directive('myDirective', [
function () {
function postLink(scope, jqElm, attr) { }
function postCompile(tElement, tAttrs) {
return function postLink(scope, jqElm, attr) {
attr.$observe("id", function (id) { // called on on init
scope.id = id !== undefined ? id : 'something 1';
});
}
}
function Ctrl(scope) {
}
return {
template:
'<div id="{{ id }}">' +
'</div>',
controller: [
'$scope', Ctrl
],
replace: true,
scope: {
id: '#'
},
restrict: 'AC',
link: postLink,
compile: postCompile
};
}
])
;
})(window.angular)
I think using id is special since its a valid DOM attribute. In my case id was also getting added as an attribute to the directive html, not the inner child where I was using it.
I created a new attribute called input-id that doesn't suffer from this name collision.
<autosuggest input-id="country"></autosuggest>
The produced markup is:
<div class="autosuggest"><input id="country"></div>
...which is what I think you are after.
The scope block for the directive looks like this:
scope: {
inputId: '#'
}
One possible solution is to disable the automatic data binding (scope: {}) and do it manually in your link function.
Check this fiddle.
module.directive('myDialog', function () {
return {
replace: true,
restrict: 'E',
scope: {},
template: '<div>Test {{a1}}</div>',
link: function (scope, element, attrs) {
if (!attrs.a1) {
scope.a1 = "default";
} else {
scope.a1 = attrs.a1;
}
}
}
});
I had the following directive:
app.directive('markdown', function () {
var converter = new Showdown.converter();
return {
restrict: 'E',
link: function (scope, element, attrs) {
var htmlText = converter.makeHtml(attrs.abc);
element.html(htmlText);
}
}
});
Which was not working correctly so the following was proposed.
app.directive('markdown', function () {
var converter = new Showdown.converter();
return {
restrict: 'E',
scope: {
input: '=input'
},
link: function (scope, element, attrs) {
scope.$watch(input,function(newvalue){
element.html(newvalue);
});
}
}
});
and I am calling it like this:
<markdown input="{{ q.qv.text }}"></markdown>
But it's giving me an error:
Error: [$parse:syntax] http://errors.angularjs.org/1.2.2/$parse/syntax?p0=q.qv.text&p1=is%20unexpe…ting%20%5B%3A%5D&p2=4&p3=%7B%7B%20q.qv.text%20%7D%7D&p4=q.qv.text%20%7D%7D
at Error (<anonymous>)
at http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:6:449
at Wa.throwError (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:152:467)
at Wa.consume (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:153:454)
at Wa.object (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:161:138)
at Wa.primary (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:152:102)
at Wa.unary (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:158:371)
at Wa.multiplicative (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:158:104)
at Wa.additive (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:157:466)
at Wa.relational (http://127.0.0.1:81/Scripts/angular-v1.2.2/angular.min.js:157:330)
Which when I click the link says:
Syntax Error: Token 'q.qv.text' is unexpected, expecting [:] at column 4 of the expression [{{ q.qv.text }}] starting at [q.qv.text }}].
IMHO, You should use.
<markdown input="q.qv.text"></markdown>
You don't need {{}}
Use, As per comment. This gives a new error "ReferenceError: input is not defined" pointing to the line "scope.$watch(input,function(newvalue){"
scope.$watch(function(){
return scope.input;
},function(newvalue){
element.html(newvalue);
});
EDIT
Complete Directive code
app.directive('markdown', function () {
return {
restrict: 'E',
scope: {
input: '=input'
},
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.input;
}, function (newvalue) {
element.html(+newvalue * 6);
});
}
}
});
Usage
<markdown input="myVar"></markdown>
Plunker DEMO
Having a directive in angular that is a reusable component, what is the best practice to expose a public API that can be accessed from the controller?
So when there are multiple instances of the component you can have access from the controller
angular.directive('extLabel', function {
return {
scope: {
name: '#',
configObj: '='
},
link: function(scope, iElement, iAttrs) {
// this could be and exposed method
scope.changeLabel = function(newLabel) {
scope.configObj.label = newLabel;
}
}
}
});
Then when having:
<ext-label name="extlabel1" config-obj="label1"></ext-label>
<ext-label name="extlabel2" config-obj="label2"></ext-label>
<ext-label name="extlabel3" config-obj="label3"></ext-label>
How can I get the access the scope.changeLabel of extLabel2 in a controller?
Does it make sense?
Does this work for you?
angular.directive('extLabel', function() {
return {
restrict: 'E',
scope: {
api: '='
},
link: function(scope, iElement, iAttrs) {
scope.api = {
doSomething: function() { },
doMore: function() { }
};
}
};
});
From containing parent
<ext:label api="myCoolApi"></ext:label>
And in controller
$scope.myCoolApi.doSomething();
$scope.myCoolApi.doMore();
I like Andrej's and use this pattern regularly, but I would like to suggest some changes to it
angular.directive('extLabel', function {
return {
scope: {
api: '=?',
configObj: '='
},
// A controller, and not a link function. From my understanding,
// try to use the link function for things that require post link actions
// (for example DOM manipulation on the directive)
controller: ['$scope', function($scope) {
// Assign the api just once
$scope.api = {
changeLabel: changeLabel
};
function changeLabel = function(newLabel) {
$scope.configObj.label = newLabel;
}
}]
}
});
<ext-label name="extlabel1" config-obj="label1"></ext-label>
<ext-label api="label2api" name="extlabel2" config-obj="label2"></ext-label>
<ext-label name="extlabel3" config-obj="label3"></ext-label>
In controller of course label2api.changeLabel('label')
I faced this problem when writing a directive to instantiate a dygraph chart in my Angular applications. Although most of the work can be done by data-binding, some parts of the API require access to the dygraph object itself. I solved it by $emit()ing an event:
'use strict';
angular.module('dygraphs', []);
angular.module('dygraphs').directive('mrhDygraph', function ($parse, $q) {
return {
restrict: 'A',
replace: true,
scope: {data: '=', initialOptions: '#', options: '='},
link: function (scope, element, attrs) {
var dataArrived = $q.defer();
dataArrived.promise.then(function (graphData) {
scope.graph = new Dygraph(element[0], graphData, $parse(scope.initialOptions)(scope.$parent));
return graphData.length - 1;
}).then(function(lastPoint) {
scope.graph.setSelection(lastPoint);
scope.$emit('dygraphCreated', element[0].id, scope.graph);
});
var removeInitialDataWatch = scope.$watch('data', function (newValue, oldValue, scope) {
if ((newValue !== oldValue) && (newValue.length > 0)) {
dataArrived.resolve(newValue);
removeInitialDataWatch();
scope.$watch('data', function (newValue, oldValue, scope) {
if ((newValue !== oldValue) && (newValue.length > 0)) {
var selection = scope.graph.getSelection();
if (selection > 0) {
scope.graph.clearSelection(selection);
}
scope.graph.updateOptions({'file': newValue});
if ((selection >= 0) && (selection < newValue.length)) {
scope.graph.setSelection(selection);
}
}
}, true);
scope.$watch('options', function (newValue, oldValue, scope) {
if (newValue !== undefined) {
scope.graph.updateOptions(newValue);
}
}, true);
}
}, true);
}
};
});
The parameters of the dygraphCreated event include the element id as well as the dygraph object, allowing multiple dygraphs to be used within the same scope.
In my opinion, a parent shouldn't access a children scope. How would you know which one to use and which one to not use. A controller should access his own scope or his parent scopes only. It breaks the encapsulation otherwise.
If you want to change your label, all you really need to do is change the label1/label2/label3 variable value. With the data-binding enabled, it should work. Within your directive, you can $watch it if you need some logic everytime it changes.
angular.directive('extLabel', function {
return {
scope: {
name: '#',
configObj: '='
},
link: function(scope, iElement, iAttrs) {
scope.$watch("configObj", function() {
// Do whatever you need to do when it changes
});
}
}
});
Use these directives on the element that you want to go prev and next:
<carousel>
<slide>
<button class="action" carousel-next> Next </button>
<button class="action" carousel-prev> Back </button>
</slide>
</carousel>
.directive('carouselNext', function () {
return {
restrict: 'A',
scope: {},
require: ['^carousel'],
link: function (scope, element, attrs, controllers) {
var carousel = controllers[0];
function howIsNext() {
if ((carousel.indexOfSlide(carousel.currentSlide) + 1) === carousel.slides.length) {
return 0;
} else {
return carousel.indexOfSlide(carousel.currentSlide) + 1;
}
}
element.bind('click', function () {
carousel.select(carousel.slides[howIsNext()]);
});
}
};
})
.directive('carouselPrev', function () {
return {
restrict: 'A',
scope: {},
require: ['^carousel'],
link: function (scope, element, attrs, controllers) {
var carousel = controllers[0];
function howIsPrev() {
if (carousel.indexOfSlide(carousel.currentSlide) === 0) {
return carousel.slides.length;
} else {
return carousel.indexOfSlide(carousel.currentSlide) - 1;
}
}
element.bind('click', function () {
carousel.select(carousel.slides[howIsPrev()]);
});
}
};
})