I'm wondering if there is a template parsing hook in Angular, that you can use globally or in specific controllers.
What I want to do is to implement a language and device specific (multidimensional) theme loader, that will dynamically grab any ressource link (img-tags, inline-styles) and redirect to the specific resource.
For example:
Someone implemented a template that shows some image:
<img src="images/my-super-image.jpg">
Now I want to grab the template and change the ressource to it's language specific correspondant:
<img src="theme/en_us/lowres/my-super-image.jpg">
The following things are important for me:
The one who generates the template doesn't need to take care of the themes, just uses the ressource as given in the first example
I don't want to use a directive, I want a global (App specific) solution --> best would be to have it in the run()-function of the app
I don't want to use look-up tables for the ressources, just want it to be highly dynamical
At the moment I'm not quite sure where to place such a parse-hook-function, nor how to get access to the current templates used on a page, to manipulate them before Angular provides them to the DOM.
I used some dirty hack, but I'm unhappy with it, because it will only be applied, when templates are already rendered and provided:
$(document).bind('DOMNodeInserted', function(event) {
if(angular.isDefined(event.originalEvent.originalTarget.innerHTML)) {
event.originalEvent.originalTarget.innerHTML = String(event.originalEvent.originalTarget.innerHTML).replace('src="images','src="' + imgPath);
}
});
Do you have any idea of how to do it? Thank you, guys!
Btw. I'm pretty new to Angular, so if you'd please be very descriptive, that would be kind. Thanks again.
You can use compile, Since angular only allow directives to modify DOM you need to create a directive
Here is an example
app.directive('myApp', function() {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs) {
},
compile: function(tElement, tAttrs, transclude) {
tElement.find('img')[0]['src'] = "theme/en_us/lowres/" + tElement.find('img')[0] ['src'].split('/')[tElement.find('img')[0]['src'].split('/').length - 1];
}
};
});
Plunker
Related
I've started work on an e-learning delivery platform and we've chosen Angularjs (1.2.29 because we still have users on IE8). Our team is all relatively new to Angular and we're not sure what is best practise to deliver the system to brief.
The aim is to have a very component-based structure, where designers can simply edit a json file, adding named components as they require them. Behind the scenes, each component should have its own html template, js functionality file and css.
We have a working system which so far includes 'paragraph' and 'image' components. The next step was to add a 'popup' component that has some interactive functionality.
The problem I can already see forming is that we're adding the component functionality into the 'pageController' in our app.js file, which I suspect is a very bad idea, not least because if we keep adding each component's functionality there, the file will become huge and unwieldy. Here's the pageController in app.js, so far:
app.controller('pageCtrl', ['$scope', '$routeParams', '$http', 'content', function($scope, $routeParams, $http, content) {
$http.get('json/page' + $routeParams.pageId + '.json')
.success(function(data) {
$scope.page = data;
});
$scope.getStyle = function(singleCase, device) {
if (singleCase == undefined)
return '';
return assignBootstrap(singleCase, device);
}
// function for new, interactive 'popup' component
$scope.showPopup = function (showOnClick) {
// presentation logic located here. This is a bad idea, right?
if ($('#'+showOnClick).hasClass('overlay')) {
$('#page_container').append($('#'+showOnClick));
}
$( '#' + $( '#' + showOnClick ).attr('data-replaces') ).remove();
$('.popup').addClass("hidden");
$('#'+showOnClick).removeClass("hidden");
}
$scope.pageId = $routeParams.pageId;
}]);
I have read and watched a lot of tutorials, and pages on the Angular site, but comprehending how to get the specific requirements of our project working with Angular is proving difficult.
This page...
https://code.angularjs.org/1.2.29/docs/guide/controller
...tells me that DOM manipulation code should be encapsulated in directives (either custom or built in, I assume).
Given that we want to end up with small .js files associated with each required component, should we instead refactor the design to use custom element (restrict: "E") directives to encapsulate the functionality?
The information I've encountered is so concept-based and abstract, it is difficult to know how the concepts should be best applied to a working project.
Is it a good use of 'element restricted' directives (effectively custom html tags) to encapsulate our individual components' code? I can imagine ending up with a list of custom html tags that define the components we need. Is that even what element directives are for?
Thanks.
The answer to your question is yes, that's the purpose of directives: inject in your HTML some reusable components in an intelligent way.
Think if you ever need to bind a variable to your "components": you'll be able to do it easily and with no pain at all, by using directives/components.
This way of using your views goes against the angular way of things:
$scope.showPopup = function (showOnClick) {
// presentation logic located here. This is a bad idea, right?
if ($('#'+showOnClick).hasClass('overlay')) {
$('#page_container').append($('#'+showOnClick));
}
$( '#' + $( '#' + showOnClick ).attr('data-replaces') ).remove();
$('.popup').addClass("hidden");
$('#'+showOnClick).removeClass("hidden");
}
because you'll end up replicate this code all over your controllers.
.
Alternatively, if you don't need any form of logic inside your "containers", you could use ng-include with templates to inject html in your pages, like this:
<div ng-include"myContainer.html"></div>
and somewhere in your html pages, include a script
<script type="text/ng-template" id="myContainer.html">
<!-- content -->
</script>
I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle
In a previous question, I was having problems getting a complex widget to properly work.
After waking up with a clear head, I decided that I should start with a sanity check: can I get a basic directive to properly work? Given that I have written directives in the past, I foresaw no difficulty.
This is the basic plunker I wrote, with only two very basic use cases.
app.directive('TestDirective',
function () {
return {
template: '<strong>This is a test directive.</strong>'
};
}
);
app.directive('SecondTestDirective',
function () {
templateUrl: 'directiveTest.html'
}
);
This is not a sane case, apparently. I'm using Angular 1.2.20, but apparently, neither a very basic directive with a hardcoded template, or a basic directive with a URL reference to a hardcoded template, are working properly. As this is a very basic case, my question: am I doing anything wrong? Should I open a bug on Angular's GitHub project?
Your problem is simple: first letter of directive name must be lowercase.
For example, instead of SecondTestDirective use secondTestDirective
While matching directives, Angular strips the prefix x- or data- from element/attribute names. Then it converts - or : delimited strings to camelCase and matches with the registered directives. That’s why we have used the secondTestDirective directive as second-test-directive in the HTML.
There were several things wrong with your code, this is the fixed version of it:
app.directive('testDirective',
function () {
return {
restrict: 'AE',
template: '<strong>This is a test directive.</strong>'
};
}
);
app.directive('secondTestDirective',
function () {
return{
restrict: 'AE',
templateUrl: 'directiveTest.html'
}
}
);
Example
Things that were wrong:
The names of the directives should have a camel-case format (the first character should be lowercase)
The second directive was not returning an object
If you are going to use your directive as Elements, you should specify that in the restrict attribute.
I have the following simple base directive:
angular.module("base", [])
.directive("base", function() {
return {
restrict: "A",
scope: true,
controller: function($scope) {
this.setHeader = function(header) {
$scope.header = header;
}
this.setBody = function(body) {
$scope.body = body;
}
this.setFooter = function(footer) {
$scope.footer = footer;
}
},
templateUrl: "base.html"
}
});
I am passing data to this directive in the following way:
.directive("custom", function() {
return {
restrict: "E",
require: "^base",
scope: {
ngModel: "="
},
link: function($scope, $element, $attrs, baseCtrl) {
//Do something with the data or not...
baseCtrl.setHeader($scope.ngModel.header);
baseCtrl.setBody($scope.ngModel.body);
baseCtrl.setFooter($scope.ngModel.footer);
}
}
});
When I create a list of my custom directives, I notice the custom directives aren't rendering immediately. I have made a Plunker demonstrating this behavior. (You will see the list cells empty for a split second, then the directives will appear)
My goal behind this design is to reuse the template of the base directive and only pass in the data needed for display. In this simple example, $scope.data is exactly what I need to pass in but it may be the case some rules or manipulation need to happen first. Rather than have the controller which queried the data handle this, I wanted to pass it off into the directive, separating the concerns.
So my questions are:
Is there any way to make the directives render faster and avoid the flickering shown in the Plunker?
Is this a best practice for reusing directives with Angular?
The flickering is being caused by the async http request to the "base.html" file. Since the HTML for the base directive has to be loaded from the server, there will be a fraction of time where no content will be displayed.
In order to render the data, Angular will go though these 3 stages:
Fetch the HTML file/template from the server (no content will be displayed)
Compile the HTML template (the DOM is updated but the scope isn't yet linked)
Link the scope to the DOM/template (expected data is displayed)
Option 1 - Use the template attribute
Just replace the templateUrl: "base.html" for the direct HTML content:
//templateUrl: "base.html"
template: '<div class="base"><div class="header bottom-border"><h2>{{header}}</h2><div><div class="body bottom-border"><p>{{body}}</p></div><div class="footer">{{footer}}</div></div>',
You will notice that there won't be any flickering this time (check this plunker).
Option 2 - Pre-load template files
Angular has a built-in template cache ($templateCache) that it uses to check if any HTML template file/content has already been fetched from the server or not. If you populate that cache while the app is loading then Angular will not need to fetch the template to render the directive, it will read it directly from the cache.
You can use Angular's $templateRequest (if you are using the latest beta version of Angular) or $templateCache (for any version).
The difference is that $templateRequest automatically makes the HTTP GET request and stores the result in the $templateCache. On the other one you will have to do it manually, like this:
loadTemplate = function(tpl) {
$http.get(tpl, { cache : $templateCache })
.then(function(response) {
$templateCache.put(tpl, html);
return html;
});
};
loadTemplate('base.html');
Note however that this approach needs your app to have a "loading phase".
Regarding the best practices for reusing directives, you seem to be on the right path. The example is to simple to give any advices... Nevertheless, check the "Best practice" notes in this Angular "Creating Custom Directives" guide.
Edit
(my personal preferences regarding template vs templateUrl)
If the main goal is solely performance (i.e. page render speed) then template seems to be the best choice. Fetching one file will always be faster than fetching two... However, as the app grows, the need for a good structure is mandatory, and template files are one of the best practices when it comes to that.
Normally I follow this "rules":
If there are only a few lines of HTML in the template, then just use the template
If the HTML template will not be constantly changing over time (i.e. post structure, contact details, etc...), use template, otherwise (i.e. templates containing banners/ads) use templateUrl
If the app has a loading phase, use templateUrl with cache
I am using the angular bootstrap ui third party library as a dependency inside my angular app. I was just wondering what is the best way to add functionality to directives and controllers inside this library?
I understand that I can just edit the directives/controllers inside ui-bootstrap-tpls-0.11.0.js, but if I were to re pull the dependencies on a build server, it would wipe away my changes. If I were to update the library version it would also wipe away my changes. I'm looking for a clean way to extend functionality.
For example, if I want to do something like extend the datepicker directive to accept a customMethod or customData then use these within the linking function. What is the best way to do this?
<datepicker ng-model="dt" custom-method="myCustomMethod()"
custom-attribute="myCustomAttribute" min-date="minDate"
show-weeks="true" class="well well-sm"></datepicker>
Thanks in advance.
One option is to decorate the directive. Decoration looks something like:
angular.module('app', ['ui.bootstrap']).
config(function($provide){
// Inject provide into the config of your app
$provide.decorator('datepickerDirective', function($delegate){
// the directive is the first element of $delegate
var datepicker = $delegate[0];
// Add whatever you want to the scope:
angular.extend(datepicker.scope, {
customAttribute: '#',
customMethod: '#'
});
// Might want to grab a reference to the old link incase
// you want to use the default behavior.
var oldLink = datepicker.link;
datepicker.link = function(scope, element, attrs){
// here you can write your new link function
oldLink(scope, element, attrs);
};
return $delegate;
});
});