Utility functions for directives - angularjs

Say I want to make an angular directive that generates links to resources that look like this:
link/to/resource/1234
from an object that looks like:
resource = {
id: 1234,
otherProperty: 'foo'
}
How can I do this effectively w/ a directive? Ie, I'd like to not have to repeat the part that goes '/link/to/resource/{{id}}'. I can't seem to get that to work right. One of the several things I've tried:
app.directive('myResource', function() {
return {
restrict: 'E',
scope: {
resource: '='
},
baseUrl: 'link/to/{{resource.id}}',
template: '{{baseUrl}}'
};
});
which ends up rendering:
Other things I've tried (like making baseUrl a function/sticking it in scope) have resulted in similar things/errors.
Is there a way to get something like this to work?

One way to handle this is to use the directive's link function to set the variable up for you, like this:
link: function(scope) {
scope.baseUrl= 'link/to/'+scope.resource.id;
},
template: '{{baseUrl}}'
Here's a working fiddle
Alternatively you could use this approach:
link: function(scope) {
scope.baseUrl= 'link/to/';
},
template: '{{baseUrl}}{{resource.id}}
Here's the fiddle

just write a directive controller
app.directive('myResource', function() {
return {
restrict: 'E',
scope: {
resource: '='
},
controller: function($scope){
$scope.getBaseUrl = function(resource){
return 'link/to/'+resource.Id;
};
},
template: '<a ng-href="http://{{getBaseUrl(resource)}}">{{getBaseUrl(resource)}}</a>'
};
});
a function makes more sense,because you wont have manage resource state change.You may answer that the Id is unlikely to change but in my opinion,it's better practice in general.
http://plnkr.co/edit/I2QKNB1o8jvZf7kCDT2v?p=preview

Related

Issue with dom manipulation inside Directive link function - Angularjs

I set up a directive as follows:
.directive('ogTakeATour', function() {
return {
restrict: 'E',
replace: true,
templateUrl: '../scripts/directives/TakeATourTemplate.html',
scope: {
content: '#',
uid: '#'
},
link: function(scope) {
angular.element(scope.uid).css("top","250px");
}
};
});
Directive template looks like this:
<div id="{{uid}}" class="tourContainer">
{{content}}
</div>
And this is how I call my directive:
<og-take-a-tour content="Content goes here" uid="menuTour"></og-take-a-tour>
However for some reasons this does not apply the css to the applicable div.
angular.element(scope.uid).css("top","250px");
Why is this? Could it be that the directive does not know what the id of my element is at the time the link function is running? How would I get around this if that is the case?
Angular's jQlite does not support search by id or CSS selector. So change your code like this:
angular.element(document.querySelector('#' + scope.uid)).css("top", "250px");

How to delegate ngFocus/ngBlur to directive's template <input> element?

I'm trying to create a custom component (directive) which is composed of an <input> box and a [-] and [+] buttons. Currently, the example below only implements the input box.
So, say I have the following HTML for my directive:
<my-input ng-blur="onBlur($event)" ng-focus="onFocus($event)"></my-input>
And for testing purposes, I use this code:
app.run(function ($rootScope) {
$rootScope.onBlur = function ($event) {
console.log('onBlur', $event);
};
$rootScope.onFocus = function ($event) {
console.log('onFocus', $event);
};
});
Now I want to create my custom <my-input> directive which has an <input> box on the template and I need the ng-blur and ng-focus set on <my-input> to respond to blur/focus events on the input box.
I have the following solution almost working: http://codepen.io/anon/pen/KpELmj
1) I have a feeling that this can be achieved in a much better way, I just can't seem to do it. Thoughts?
2) $event seems to be undefined and I can't understand why. Thoughts?
Ok figured it out. Doron's answer was a good starting point for research, but now I think I have what you are looking for. The key is you have to use & in the link section in order to get it to execute the expression.
.directive('myInput', function($timeout) {
return {
restrict: 'E',
scope: {
data: '=',
blur: '&myBlur' //this is the key line
},
template: '<input ng-blur="blur($event)" ng-model="data">'
}
})
This is how you use it:
<my-input my-blur="runBlurFunc()"></my-input>
If you really want to define the function on the root scope, you can use $scope.$root.onBlur() instead of runBlurFunc()
Hope I got your question right, did you try to use the link function?
app.directive('myInput', function () {
return {
restrict: 'E',
scope: {
ngBlur: '&',
ngFocus: '&'
},
bindToController: true,
controller: controllerFn,
controllerAs: 'ctrl',
link:function(scope){
scope.onBlur = function(ev){
console.log(ev);
}
scope.onFocus = function(ev){
console.log(ev);
}
},
template: '[-]<input ng-blur="onBlur($event)" ng-focus="onFocus($event)"></input>[+]'
}
});

Angularjs directive to make any element work like a link

I tried to write a directive making an arbitrary element work like a link. Something like
<span goto="overview">Overview</span>
to be translated into something like
<span ng-click="$location.path('#/overview')">Overview</span>
or whatever will work. As a bonus I'd like the link to work, even when the current URL is already .../#overview.1
This sound pretty trivial, but I'm stuck with something like
.directive('goto', function($location) {
return {
template: function(element, attrs) {
// Here `attrs` should be used but how?
var ref = "#/" + element.attr('goto');
// This is a mystery to me.
// I guess, transclusion is the way to go, but how exactly?
var elementContent = getEverythingBelowElementButHow();
// I doubt `$location` will be visible on the invocation site.
return "<span ng-click='$location.path(" +
ref + "'>" +
elementContent +
"</span>";
}
};
})
I'm perfectly sure, I'm doing it all wrong. I'm nearly sure it's all unnecessary complicated. However, I'm hoping to be able to learn something from the answers.
1I found myself repeatedly clicking on such a link in order to get to the "pristine overview state" and wondering that nothing happens. Obviously, the browser ignores links to the current location, but there could be a way around this?
You can add click event to directive dynamically.
angular.module('ExApp')
.directive('goto', function($location) {
return {
restrict: 'A',
scope: {
//scope variables
},
link: function(scope, element, attr) {
element.on('click', function() {
$location.path('/overview');
scope.$apply();
});
}
}
});
plunker : http://plnkr.co/edit/n6VbZnAVzM4y0p8dbmvf?p=preview
Take a look at this example:
app.directive('goto', function($location){
function link(scope, element, attr){
scope.gotoLink = function(){
alert(scope.goto);
//$location.path(scope.goto);
};
}
return {
restrict: 'A',
transclude: true,
scope:{ goto: '#' },
template: '<span ng-click="gotoLink()"><span ng-transclude></span></span>',
link: link
};
});
You can play with it here (plnkr)

Dynamic template in directive based on attributes?

Ive seen a bunch of questions pretty similar to this, but I'm new to Angular so they aren't quite making sense. Here's my sitaution:
I have a directive defined:
robus.directive("titlebar", function() {
return {
restrict: "E",
scope: { title: '#title' },
template: "<header class='bar-title'><h1 class='title'>{{title}}</h1></header>",
replace: true
}
});
I use this directive like this:
<titlebar title="{{workout.name}}"></titlebar>
Ideally, I want to add optional attributes into this, like:
<titlebar title="{{workout.name}}" editButton="true" closeButton="true"></titlebar>
How do I handle these in the template definition? I've been reading about a $compile() function that I need to override, but haven't been clear on how to do so. The templates are just simple strings, so I feel like I can just do them inline versus referencing them as separate files.
Thanks!
Make them accessible within the directive by adding them to the scope statement, just as you have the title. Then add the buttons to the template, and conditionalize them like so:
robus.directive("titlebar", function() {
return {
restrict: "E",
scope: { title: '#title', edit: '#editButton', cancel: '#cancelButton' },
template: "<header class='bar-title'><h1 class='title'>{{title}}</h1><span ng-show='edit'>Edit</span><span ng-show='cancel'>Cancel</span></header>",
replace: true
}
});
<titlebar title="{{workout.name}}" edit-button="true" cancel-button="false"></titlebar>
Note that it's editButton in the directive and edit-button in the HTML; there's a built-in conversion from hyphenated to camel-case that will bite you if you're not aware of it.
Also, I recommend the use of transclude here, as I think it will read a bit more cleanly:
robus.directive("titlebar", function() {
return {
restrict: "E",
scope: { edit: '#editButton', cancel: '#cancelButton' },
template: "<header class='bar-title'><h1 class='title' ng-transclude></h1><span ng-show='edit'>Edit</span><span ng-show='cancel'>Cancel</span></header>",
transclude: true,
replace: true
}
});
<titlebar edit-button="true" cancel-button="false">{{workout.name}}</titlebar>

Dynamically register directive at runtime

Normally, directives are registered on a module by using the directive method:
.directive('MyDirective', function (MyDep) {
return {
restrict: 'E',
template: '<div></div>',
controller: function ($scope) {
}
}
});
However, what if I dynamically want to register directives during runtime? For example, say a user pulls the following data from the server:
{
directives: {
dir1: {
restrict: 'E',
template: '<div></div>',
},
dir2: {
restrict: 'E',
template: '<div></div>',
}
}
}
Is there any way I can take this data use it to dynamically register new directives for my app? If successful, I should then be able to dynamically generate and $compile HTML which depends on them.
This is a subset of the "lazy loading Angular artifacts" problem (I have explored here and there exist other resources too). An idea is to use a config function to "steal" the $compileProvider (ref) and then call $compileProvider.directive(...) on demand, based on your data.
A rough sketch of the idea is:
var cachedCompileProvider;
angular.module(...).config(['$compileProvider', function($compileProvider) {
cachedCompileProvider = $compileProvider;
});
And then (e.g. from inside someplace that has access to $http):
$http.get(...).then(function(response) {
angular.forEach(response.data.directives, function(dirDefinition, dirName) {
cachedCompileProvider.directive(dirName, dirDefinition);
});
});
(Of course you cannot receive controller functions from a JSON response like the above, you will have to use other techniques - but hopefully you get the idea.)

Resources