I have just discovered AngularJS and am in what seems to be a fairly steep learning curve. Can anyone recommend a couple of good books that will take a "practical" dive into AngularJS. My programming question is this:
Consider:
<input type="text" name="inputField1" ng-model="myModel.firstName"
encrypt-and-save="myModel.encryptedFirstName" />
In my directive named "encryptAndSave" I want to dynamically bind to the model property whose name (in this case) is "encryptedFirstName". Everything that I have read seems to say that this is possible, but I haven't found a concrete example of how it is done. Any help/pointers would be greatly appreciated.
Thanks in advance,
Jimmy
Here's what I wound up doing. I discovered $parse and .assign. I used the $parse at initialization and .assign for late/real-time binding. Does this make sense, or have I totally missed something?
app.directive('encryptAndSave', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var encryptedModelValue = $parse(attrs.encryptAndSave);
//
// wait for model change (could also wait for blur??)
//
scope.$watch(attrs.ngModel, function(newValue, oldValue) {
var encrValue = encryptThis(newValue);
encryptedModelValue.assign(scope, encrValue);
});
}
};
});
Thanks again for your help,
Jimmy
In you directive create isolated scope and use 2 way binding via '='
scope: {
encryptAndSave: '='
}
Please see demo below
var app = angular.module('app', []);
app.controller('firstCtrl', function($scope) {
$scope.myModel = {
firstName: "joe",
encryptedFirstName: " encrypted joe"
};
});
app.directive('encryptAndSave', function() {
return {
scope: {
encryptAndSave: '='
},
link: function(scope, elem, attr) {
alert(scope.encryptAndSave)
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="firstCtrl">
<input type="text" name="inputField1" ng-model="myModel.firstName" encrypt-and-save="myModel.encryptedFirstName" />
</div>
</body>
I'm all about the watchers for this one. The reason is that you can't access dynamic members of the parent scope using an isolated scope. I may be wrong, but it looks like this is what you're trying to do.
angular.module("encryptMe", [])
.directive("encryptAndSave", function() {
function hash(name) {
return name;
}
return {
link: function(scope, element, attrs) {
scope.$watch("firstName", function() {
scope[attrs.encryptAndSave] = hash(scope.firstName + "");
});
}
};
})
.controller("encryptController", function($scope) {
$scope.firstName = "Who?";
});
Naturally, you would want to more interesting stuff in your hash function. The main thing here is that this directive pays attention to the value passed in when declaring the directive (encrypt-and-save=[your variable here]). It then watches the firstName variable and updates the given parent-scope variable with the hash of the new value.
Feel free to try it in a fiddle.
Related
I've got next directive:
(function() {
'use strict';
angular
.module('myApp')
.directive('inner', inner);
function inner () {
return {
restrict: 'A',
scope: false,
link: linkFunc
};
function linkFunc (scope, element, attrs) {
}
}
})();
And HTML:
<span inner>{{vm.number}}</span>
How can I access vm.number's value in linkFunc? I need to take value exactly from content of the span tag.
There are various ways you can do this but here are the 2 most common ways:
ngModel
You could use ng-model like so in your template:
<span inner ng-model="vm.number">{{vm.number}}</span>
In your directive you require the ngModel where you can pull its value:
.directive( 'inner', function(){
return {
require: 'ngModel',
link: function($scope, elem, attrs, ngModel){
var val = ngModel.$modelValue
}
}
})
declare isolate scope properties
<span inner="vm.number">{{vm.number}}</span>
.directive( 'inner', function(){
return {
scope: { inner:'=' } ,
link: function($scope, elem, attrs){
var val = $scope.inner
}
}
})
Some less common ways:
use $parse service to get the value
Using the template again:
<span inner="vm.number">{{vm.number}}</span>
Let's assume you're going to Firstly you'll need to inject the $parse service in your directive's definition. Then inside your link function do the following:
var val = $parse(attrs.inner)
inherited scope for read only
I don't recommend this, because depending on how you defined your directive's scope option, things might get out of sync:
isolate (aka isolated) scopes will not inherit that value and vm.number will probably throw an undefined reference error because vm is undefined in most cases.
inherited scope will inherit the initial value from the parent scope but could diverge during run-time.
no scope will be the only case where it will stay in sync since the directive's $scope reference is the same scope present in the expression {{vm.number}}
Again I stress this is probably not the best option here. I'd only recommend this if you are suffering performance issues from a large number of repeated elements or large number of bindings. More on the directive's scope options - https://spin.atomicobject.com/2015/10/14/angular-directive-scope/
Well, In Angular directive, Link function can do almost everything controller can.
To make it very simple, we use one of them most of the time.
var app = angular.module('app', []);
app.controller('AppCtrl', function ($scope) {
$scope.number = 5;
}).directive('inner', function () {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
var number = scope.number;
console.log(number);
}
}
});
Inside html :
<div inner ng-model="number">{{number}}</div>
https://plnkr.co/edit/YbXYpNtu7S3wc0zuBw3u?p=preview
In order to take value from HTML, Angular provides ng-model directive which is works on two way data binding concepts.
There are other ways which is already explain by #jusopi :)
cheers!
Suppose I have something like this:
<div my-custom-root="root">
<input type="text" my-custom-Path="path.to.somewhere" />
</div>
And now I want to translate this to something which essentially behaves equivilant to:
<input type="text" ng-model="root.path.to.somewhere" />
I get so far as to specify the two directives, get all the objects etc. These supply me with the root object and the path, but how do create a binding from those? I am missing the generation of the appropriate ng-model directive or the use of the NgModelController directly.
I created a plunkr here with the stuff I managed to put together so far.
For easier reference here is my code of the directives just like they can be found in my plunkr as well:
app.directive('myCustomRoot', function() {
var container;
return {
controller: function($scope) {
this.container = function() {
return container;
};
},
compile: function() {
return function ($scope, elm, attrs) {
$scope.$watch(attrs.myCustomRoot, function(newVal) {
container = newVal;
});
};
}
};
});
app.directive('myCustomPath', function($parse) {
return {
require: '^myCustomRoot',
link: function(scope, element, attrs, containerController) {
var getter = $parse(attrs.myCustomPath);
scope.$watch(function() {
return containerController.container();
},
function(newVal) {
if (newVal) {
alert('The value to be edited is: ' + getter(newVal) + '\nIt is the property "' + attrs.myCustomPath + '"\non Object: ' + JSON.stringify(newVal) + '\n\nNow how do I bind this value to the input box just like ng-model?');
}
}
);
}
};
});
You can see that I have all the things available in my alert-Box, but I don't know how to do the binding.
I hoped that there was some way to write someBindingService.bindInput(myInput, myGetter, mySetter), but I have done quite a lot of source code reading and unfortunately the binding is coupled closely to the compilation.
However thanks to the question "Add directives from directive in AngularJS" I managed to figure out a way which is less elegant but it is compact and effective:
app.directive('myCustomPath', function($compile, $parse) {
return {
priority: 1000,
terminal: true,
link: function(scope, element, attrs, containerController) {
var containerPath = element.closest('[my-custom-root]').attr('my-custom-root');
attrs.$set('ngModel', containerPath + '.' + attrs['myCustomPath']);
element.removeAttr('my-custom-path');
$compile(element)(scope);
}
}
});
This uses a little bit of jQuery, but it should not be to hard to do it with plain jQLite as well.
I'm trying to use prettyprint plugin for my angularjs app.
But cannot make it works. I create a simple directive and call method prettyPrint(), but the code is not formatted.
FIDDLE: http://jsfiddle.net/Tropicalista/yAv4f/2/
App.directive('test', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).prettyPrint()
}
};
});
I modified your code and i'll update here:
http://jsfiddle.net/yAv4f/6/
html:
<div ng-app="Knob" ng-controller="myCtrl">
<pre class="prettyprint linemus"></pre>
<pre class="prettyprint linemus"><!DOCTYPE html><html lang="en"></html></pre>
</div>
javascript:
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.dom = '<!DOCTYPE html><html lang="en"></html>'
})
App.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(scope.dom));
}
};
});
Basically, you need to use the file prettify.js to control the execution of the prettify() function, with prettyPrintOne() you can execute it in a specific html text.
And to simplify the use of the directive, like prettify stlyle, i'll suggest restric to 'C' a class and change the the directive name to 'prettyprint'
I've expanded on the previous answers and created a jsfiddle with a working directive that responds in realtime to model changes:
http://jsfiddle.net/smithkl42/cwrgLd0L/27/
HTML:
<div ng-app="prettifyTest" ng-controller="myCtrl">
<div>
<input type="text" ng-model="organization.message" />
</div>
<prettify target="organization"><pre><code class="prettyprint">console.log('{{target.message}}');
</code>
</pre>
</prettify>
</div>
JS:
var App = angular.module('prettifyTest', []);
App.controller('myCtrl', function ($scope) {
$scope.organization = {
message: 'Hello, world!'
};
});
App.directive('prettify', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'E',
scope: {
target: '='
},
link: function (scope, element, attrs) {
var template = element.html();
var templateFn = $compile(template);
var update = function(){
$timeout(function () {
var compiled = templateFn(scope).html();
var prettified = prettyPrintOne(compiled);
element.html(prettified);
}, 0);
}
scope.$watch('target', function () {
update();
}, true);
update();
}
};
}]);
h/t to #DanielSchaffer (see Template always compiles with old scope value in directive).
Angular already has this filter built-in for JSON:
<pre>
{{data | json}}
</pre>
If you want to make your own directive, you can use the JSON object directly:
app.filter('prettyJSON', function () {
function syntaxHighlight(json) {
return JSON ? JSON.stringify(json, null, ' ') : 'your browser doesnt support JSON so cant pretty print';
}
return syntaxHighlight;
});
With markup
<pre>
{{data | prettyJSON}}
</pre>
I would like to make a small addition to the directive by #carlosmantilla
You can achieve the same thing without creating the scope variable. I have added this correction on github
This should work properly I assume.
http://jsfiddle.net/yAv4f/143/
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.text = "function f1(){int a;}";
})
function replaceText(str)
{
var str1 = String(str);
return str1.replace(/\n/g,"<br/>");
}
app.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(replaceText(element.html()),'',true));
}
};
});
I struggled with this issue for quite a while and wanted to chime in here, albeit much later than everyone else (for real though, who's still using AngularJS in late 2017? This guy.) My specific use-case was where I have code (xml) being dynamically loaded on the page which needed to be pretty printed over and over again.
This directive will take in your code as an attribute, remove the prettyprinted class that's added to the element right after you run prettyPrint(). It will watch for changes on the inputted code from the parent's scope and run the code again when changes occur.
Only dependency is that you have Google's code-prettify. I had it self-hosted, hence the PR.prettyPrint() (as instructed in the docs as of sept 2017).
The directive fully encapsulates the needed Google code-prettify functionality for dynamic content.
angular.module('acrSelect.portal.directives')
.directive('prettyPrint', ['$timeout', function($timeout) {
return {
restrict: 'E',
scope: {
'code': '=',
},
template: '<pre ng-class="{prettyprint: code}">{{ code }}</pre>',
link: function (scope, element, attr) {
scope.$watch('code',function(){
$timeout(function() {
//DOM has finished rendering
PR.prettyPrint();
element.find(".prettyprint").removeClass("prettyprinted");
});
});
}
}
}
]);
The html in the parent template might look
<pretty-print code="selectedCode" ng-show="codeIsSelected"></pretty-print>
Hope this helps another poor soul!
I am trying to create a directive that wraps the twitter typeahead plugin. What I have so far is:
HTML:
<input ng-twitter-typeahead type="text" ng-model="participant" data="exampleData" />
{{ participant }}
I want the value for 'participant' to be updated when I select something on the typeahead. The typeahead itself works properly, but I can't capture the selected value. Below is the javascript:
var app = angular.module('myApp', [])
app.directive('ngTwitterTypeahead', function () {
return {
restrict: 'EA',
scope: {
data: '='
},
link: function ($scope, $element, $attrs) {
$element.typeahead($scope.data);
$element.bind('typeahead:selected', function(obj, datum) {
// I really don't know how to do this part
// the variable 'datum' is what I want to be passed to ng-model
// I tried things like:
// Including the ngModelController and typing:
// ngModel.$setViewValue(datum)
// but that didn't work.
}
};
});
I'm obviously missing something fundamental when it comes to AngularJS. Any help would be greatly appreciated!
EDIT **
I found the solution. I am clueless sometimes:
angular.module('siyfion.ngTypeahead', [])
.directive('ngTypeahead', function () {
return {
restrict: 'C',
scope: {
datasets: '=',
ngModel: '='
},
link: function ($scope, $element, $attrs) {
$element.typeahead($scope.datasets);
$element.bind('typeahead:selected', function(obj, datum) {
$scope.$apply(function() {
$scope.ngModel = datum;
});
})
}
};
});
You could require ngModel controller inside the directive. It will give you an access to the model controller inside the link function, see http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController
Here you can find an example how to use it the real life http://suhairhassan.com/2013/05/01/getting-started-with-angularjs-directive.html#.UhSdDOZdXUE
The twitter Typeahead project seems to be abandoned.
But you can use the Typeahead Angular directive from the Angular UI Bootstrap library that is being actively maintained as of now.
I have a very boiled down version of what I am doing that gets the problem across.
I have a simple directive. Whenever you click an element, it adds another one. However, it needs to be compiled first in order to render it correctly.
My research led me to $compile. But all the examples use a complicated structure that I don't really know how to apply here.
Fiddles are here: http://jsfiddle.net/paulocoelho/fBjbP/1/
And the JS is here:
var module = angular.module('testApp', [])
.directive('test', function () {
return {
restrict: 'E',
template: '<p>{{text}}</p>',
scope: {
text: '#text'
},
link:function(scope,element){
$( element ).click(function(){
// TODO: This does not do what it's supposed to :(
$(this).parent().append("<test text='n'></test>");
});
}
};
});
Solution by Josh David Miller:
http://jsfiddle.net/paulocoelho/fBjbP/2/
You have a lot of pointless jQuery in there, but the $compile service is actually super simple in this case:
.directive( 'test', function ( $compile ) {
return {
restrict: 'E',
scope: { text: '#' },
template: '<p ng-click="add()">{{text}}</p>',
controller: function ( $scope, $element ) {
$scope.add = function () {
var el = $compile( "<test text='n'></test>" )( $scope );
$element.parent().append( el );
};
}
};
});
You'll notice I refactored your directive too in order to follow some best practices. Let me know if you have questions about any of those.
In addition to perfect Riceball LEE's example of adding a new element-directive
newElement = $compile("<div my-directive='n'></div>")($scope)
$element.parent().append(newElement)
Adding a new attribute-directive to existed element could be done using this way:
Let's say you wish to add on-the-fly my-directive to the span element.
template: '<div>Hello <span>World</span></div>'
link: ($scope, $element, $attrs) ->
span = $element.find('span').clone()
span.attr('my-directive', 'my-directive')
span = $compile(span)($scope)
$element.find('span').replaceWith span
Hope that helps.
Dynamically adding directives on angularjs has two styles:
Add an angularjs directive into another directive
inserting a new element(directive)
inserting a new attribute(directive) to element
inserting a new element(directive)
it's simple. And u can use in "link" or "compile".
var newElement = $compile( "<div my-diretive='n'></div>" )( $scope );
$element.parent().append( newElement );
inserting a new attribute to element
It's hard, and make me headache within two days.
Using "$compile" will raise critical recursive error!! Maybe it should ignore the current directive when re-compiling element.
$element.$set("myDirective", "expression");
var newElement = $compile( $element )( $scope ); // critical recursive error.
var newElement = angular.copy(element); // the same error too.
$element.replaceWith( newElement );
So, I have to find a way to call the directive "link" function. It's very hard to get the useful methods which are hidden deeply inside closures.
compile: (tElement, tAttrs, transclude) ->
links = []
myDirectiveLink = $injector.get('myDirective'+'Directive')[0] #this is the way
links.push myDirectiveLink
myAnotherDirectiveLink = ($scope, $element, attrs) ->
#....
links.push myAnotherDirectiveLink
return (scope, elm, attrs, ctrl) ->
for link in links
link(scope, elm, attrs, ctrl)
Now, It's work well.
function addAttr(scope, el, attrName, attrValue) {
el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}
The accepted answer by Josh David Miller works great if you are trying to dynamically add a directive that uses an inline template. However if your directive takes advantage of templateUrl his answer will not work. Here is what worked for me:
.directive('helperModal', [, "$compile", "$timeout", function ($compile, $timeout) {
return {
restrict: 'E',
replace: true,
scope: {},
templateUrl: "app/views/modal.html",
link: function (scope, element, attrs) {
scope.modalTitle = attrs.modaltitle;
scope.modalContentDirective = attrs.modalcontentdirective;
},
controller: function ($scope, $element, $attrs) {
if ($attrs.modalcontentdirective != undefined && $attrs.modalcontentdirective != '') {
var el = $compile($attrs.modalcontentdirective)($scope);
$timeout(function () {
$scope.$digest();
$element.find('.modal-body').append(el);
}, 0);
}
}
}
}]);
Josh David Miller is correct.
PCoelho, In case you're wondering what $compile does behind the scenes and how HTML output is generated from the directive, please take a look below
The $compile service compiles the fragment of HTML("< test text='n' >< / test >") that includes the directive("test" as an element) and produces a function. This function can then be executed with a scope to get the "HTML output from a directive".
var compileFunction = $compile("< test text='n' > < / test >");
var HtmlOutputFromDirective = compileFunction($scope);
More details with full code samples here:
http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs
Inspired from many of the previous answers I have came up with the following "stroman" directive that will replace itself with any other directives.
app.directive('stroman', function($compile) {
return {
link: function(scope, el, attrName) {
var newElem = angular.element('<div></div>');
// Copying all of the attributes
for (let prop in attrName.$attr) {
newElem.attr(prop, attrName[prop]);
}
el.replaceWith($compile(newElem)(scope)); // Replacing
}
};
});
Important: Register the directives that you want to use with restrict: 'C'. Like this:
app.directive('my-directive', function() {
return {
restrict: 'C',
template: 'Hi there',
};
});
You can use like this:
<stroman class="my-directive other-class" randomProperty="8"></stroman>
To get this:
<div class="my-directive other-class" randomProperty="8">Hi there</div>
Protip. If you don't want to use directives based on classes then you can change '<div></div>' to something what you like. E.g. have a fixed attribute that contains the name of the desired directive instead of class.