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
Related
I'm using a template called Gentelella and I'm trying to implement AngularJS into it. However, I'm having an issue with a certain Javascript file. In the end of this file, a $(document).ready function is called which initialises Javascript code that makes some changes in the HTML code. The issue is that the $(document).ready function is called too early, before the HTML is fully loaded.
This issue occurs probably because I'm using ngRoute and this injects the template html file into the ng-view of the index.html. When this happens, the DOM probably already announces a document ready before AngularJS has injected the template (=HTML).
So basically, I just need to find a way to call some code in a Javascript file once AngularJS has injected the template.
I attached some code to gain some insight into the issue:
Snippet of the custom.min.js
$(document).ready(function () {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
});
Snippet of the main.js:
.config(function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
templateUrl : 'dash.html',
controller : 'dash',
controllerAs: 'controller'
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation',
controllerAs: 'controller'
}).when('/plain_page', {
templateUrl : 'plain_page.html',
controller : 'dash',
controllerAs: 'controller'
}).otherwise('/');
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
})
Thanks in advance!
Many jQuery plugins depend on a workflow of 1. draw the DOM. 2. run an init() function to set up code against those DOM elements.
That workflow fares poorly in Angular, because the DOM isn't static: Angular sets up and destroys DOM nodes on its own lifecycle, which can overwrite event bindings or DOM changes made outside Angular. Document ready isn't particularly useful when you're using Angular, because all it indicates is that Angular itself is ready to start running.
To use Angular effectively, you have to get into the habit of initing code only when it's actually needed. So instead of a big bucket of init_foo(); init_bar(); on document.ready, you should have a Foo directive with its own init code, and a Bar directive with its own specific init code, and so on. Each of those directives should only modify the DOM created by that specific directive. This is the only safe way to ensure that the DOM elements you need to modify actually exist, and that you're not creating conflicts or unexpected interdependencies between directives.
To take one example: I'm guessing your init_flot_chart() crawls down through the DOM looking for a particular element inside of which it'll draw a flot chart. Instead of that top-down approach, create a directive:
angular.module('yourApp')
.directive('flotWrapper', function () {
return {
template: "<div></div>",
scope: {
data: '#'
},
link: function(scope, elem, attrs) {
var options = {}; // or could pass this in as an attribute if desired
$.plot(elem, scope.data, options); // <-- this calls flot on the directive's element; no DOM crawling necessary
}
};
});
which you use like this:
<flot-wrapper data="{{theChartData}}"></flot-wrapper>
...where theChartData is an object containing whatever data is to be drawn in the chart. (You can add other attributes to pass in whatever other parameters you like, such as the flot options, a title, etc.)
When Angular draws that flotWrapper directive, it first creates the DOM element(s) in the directive template, and then runs whatever is in its link function against the template's root element. (The flot library itself can be included via a plain old <script> tag so its plot function is available when the directive needs it.)
(Note that this wouldn't update automatically if the contents of theChartData change; a more elaborate example which also watches for changes and responds appropriately can be seen here.)
As you are using ngRoute, your controller will run when the page is loaded.
You can call an init() method when the controller starts to do whatever you want.
function MyCtrl($scope) {
function init() {
console.log('controller started!');
}
init();
}
As a side note, it is a best practice recommanded in John Papa Angular's guide.
Another possibily is to use the ng-init directive, for example:
<div ng-controller="MyCtrl" ng-init="myFunction()">...</div>
There is a lifecycle hooks named $postLink() in recent version of angular.js, Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation.Check the guide
This is related to angular digest cycle, it's about how angular works underneath the hood, data binding etc. There are great tutorials explaining this.
To solve your problem, use $timeout, it will make the code execute on the next cycle:
app.controller('Controller', function ($scope, $timeout) {
$scope.$on('$viewContentLoaded', function(event) {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
},0);
});
});
$document.ready(function() {
$scope.$on('$viewContentLoaded', function() {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
})
})
})
update Notice: sry guys, this is not a rigorous solution, plz see another answer I wrote
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'm using angular-translate for i18n in an AngularJS application.
For every application view, there is a dedicated controller. In the controllers below, I set the value to be shown as the page title.
Code
HTML
<h1>{{ pageTitle }}</h1>
JavaScript
.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.pageTitle = $filter('translate')('HELLO_WORLD');
}])
.controller('SecondPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.pageTitle = 'Second page title';
}])
I'm loading the translation files using the angular-translate-loader-url extension.
Problem
On the initial page load, the translation key is shown instead of the translation for that key. The translation is Hello, World!, but I'm seeing HELLO_WORLD.
The second time I go to the page, all is well and the translated version is shown.
I assume the issue has to do with the fact that maybe the translation file is not yet loaded when the controller is assigning the value to $scope.pageTitle.
Remark
When using <h1>{{ pageTitle | translate }}</h1> and $scope.pageTitle = 'HELLO_WORLD';, the translation works perfect from the first time. The problem with this is that I don't always want to use translations (eg. for the second controller I just want to pass a raw string).
Question
Is this a known issue / limitation? How can this be solved?
Recommended: don't translate in the controller, translate in your view
I'd recommend to keep your controller free from translation logic and translate your strings directly inside your view like this:
<h1>{{ 'TITLE.HELLO_WORLD' | translate }}</h1>
Using the provided service
Angular Translate provides the $translate service which you can use in your Controllers.
An example usage of the $translate service can be:
.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
$translate('PAGE.TITLE')
.then(function (translatedValue) {
$scope.pageTitle = translatedValue;
});
});
The translate service also has a method for directly translating strings without the need to handle a promise, using $translate.instant():
.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
$scope.pageTitle = $translate.instant('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});
The downside with using $translate.instant() could be that the language file isn't loaded yet if you are loading it async.
Using the provided filter
This is my preferred way since I don't have to handle promises this way. The output of the filter can be directly set to a scope variable.
.controller('TranslateMe', ['$scope', '$filter', function ($scope, $filter) {
var $translate = $filter('translate');
$scope.pageTitle = $translate('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});
Using the provided directive
Since #PascalPrecht is the creator of this awesome library, I'd recommend going with his advise (see his answer below) and use the provided directive which seems to handle translations very intelligent.
The directive takes care of asynchronous execution and is also clever enough to unwatch translation ids on the scope if the translation has no dynamic values.
Actually, you should use the translate directive for such stuff instead.
<h1 translate="{{pageTitle}}"></h1>
The directive takes care of asynchronous execution and is also clever enough to unwatch translation ids on the scope if the translation has no dynamic values.
However, if there's no way around and you really have to use $translate service in the controller, you should wrap the call in a $translateChangeSuccess event using $rootScope in combination with $translate.instant() like this:
.controller('foo', function ($rootScope, $scope, $translate) {
$rootScope.$on('$translateChangeSuccess', function () {
$scope.pageTitle = $translate.instant('PAGE.TITLE');
});
})
So why $rootScope and not $scope? The reason for that is, that in angular-translate's events are $emited on $rootScope rather than $broadcasted on $scope because we don't need to broadcast through the entire scope hierarchy.
Why $translate.instant() and not just async $translate()? When $translateChangeSuccess event is fired, it is sure that the needed translation data is there and no asynchronous execution is happening (for example asynchronous loader execution), therefore we can just use $translate.instant() which is synchronous and just assumes that translations are available.
Since version 2.8.0 there is also $translate.onReady(), which returns a promise that is resolved as soon as translations are ready. See the changelog.
EDIT: Please see the answer from PascalPrecht (the author of angular-translate) for a better solution.
The asynchronous nature of the loading causes the problem. You see, with {{ pageTitle | translate }}, Angular will watch the expression; when the localization data is loaded, the value of the expression changes and the screen is updated.
So, you can do that yourself:
.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.$watch(
function() { return $filter('translate')('HELLO_WORLD'); },
function(newval) { $scope.pageTitle = newval; }
);
});
However, this will run the watched expression on every digest cycle. This is suboptimal and may or may not cause a visible performance degradation. Anyway it is what Angular does, so it cant be that bad...
To make a translation in the controller you could use $translate service:
$translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
vm.si = translations['COMMON.SI'];
vm.no = translations['COMMON.NO'];
});
That statement only does the translation on controller activation but it doesn't detect the runtime change in language. In order to achieve that behavior, you could listen the $rootScope event: $translateChangeSuccess and do the same translation there:
$rootScope.$on('$translateChangeSuccess', function () {
$translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
vm.si = translations['COMMON.SI'];
vm.no = translations['COMMON.NO'];
});
});
Of course, you could encapsulate the $translateservice in a method and call it in the controller and in the $translateChangeSucesslistener.
What is happening is that Angular-translate is watching the expression with an event-based system, and just as in any other case of binding or two-way binding, an event is fired when the data is retrieved, and the value changed, which obviously doesn't work for translation. Translation data, unlike other dynamic data on the page, must, of course, show up immediately to the user. It can't pop in after the page loads.
Even if you can successfully debug this issue, the bigger problem is that the development work involved is huge. A developer has to manually extract every string on the site, put it in a .json file, manually reference it by string code (ie 'pageTitle' in this case). Most commercial sites have thousands of strings for which this needs to happen. And that is just the beginning. You now need a system of keeping the translations in synch when the underlying text changes in some of them, a system for sending the translation files out to the various translators, of reintegrating them into the build, of redeploying the site so the translators can see their changes in context, and on and on.
Also, as this is a 'binding', event-based system, an event is being fired for every single string on the page, which not only is a slower way to transform the page but can slow down all the actions on the page, if you start adding large numbers of events to it.
Anyway, using a post-processing translation platform makes more sense to me. Using GlobalizeIt for example, a translator can just go to a page on the site and start editing the text directly on the page for their language, and that's it: https://www.globalizeit.com/HowItWorks. No programming needed (though it can be programmatically extensible), it integrates easily with Angular: https://www.globalizeit.com/Translate/Angular, the transformation of the page happens in one go, and it always displays the translated text with the initial render of the page.
Full disclosure: I'm a co-founder :)
I have a problem that occurs sometimes, when the template entry is not defined in the template cache, it happens every once in a while after reloading the page.
The related code:
index.html
<div ng-include src="'Views/templates.html'"></div>
Views/templates.html
<script type="text/ng-template" id="customTemplate/spare-request-item.html">
<div>..</div>
</script>
directive.js
.directive("spare", function ($templateCache) {
console.log($templateCache.info());, //return length 30 when it fails, 31 otherwise
console.log($templateCache.get('customTemplate/spare-request-item.html'));//return undefined when it fails
return {
..
templateUrl: 'customTemplate/spare-request-item.html',
Anyone else has experienced this or know how to assure that the template is loaded and parsed before the directive runs? It is a angular bug?
Angular compiles the directives as it goes over DOM. You can't have it "wait" on a directive. What you could do, is not to show the directive until templates have loaded.
I don't think this is a good approach since it requires "knowledge" on the part of the user of the directive:
<div ng-include="'Views/templates.html'"
onload="$root.templatesLoaded = true"></div>
<my-directive ng-if="templatesLoaded"></my-directive>
Another approach would be to manually load all the templates and the directive's specific template. Here's a conceptual way of how to do this:
app.directive("foo", function($templateRequest, $templateCache, $compile) {
return {
link: function(scope, element) {
$templateRequest("templates.html")
.then(function(templateContainer) {
$compile(templateContainer);
return undefined; // just to show that no need to return anything
})
.then(function() {
var template = $templateCache.get("foo.html");
if (template) {
element.html(template);
$compile(element.contents())(scope);
}
});
}
}
});
plunker
$templateRequest is used here since it caches the templates.html template (to prevent double-requests), but it is a "lazy" use, since you would not actually need the template with id === "templates.html".
You can abstract it into a service, of course, to make it nicer.
Typically, however, I have seen directive developers embed the template in the .js file of the directive. This frees the user of the directive from loading separate .html files to make the directives work. I suppose you could load themes this way.
I created a directive that loads some html code inside a tag:
directive:
var appDirectives = angular.module("skdApp", ["offerServices"]);
appDirectives.directive("widget", function() {
return {
restrict: 'A',
template: '<div ng-controller="OfferCtrl"><p>{{offer.price}}</p></div>';
}
service
Besides, I'm using a service to fetch some json data
angular.module('offerServices', ['ngResource']).
factory('Offer', function ($resource) {
return $resource('url/offer.json', {},
{
get: {method: 'JSONP', params: {callback: 'JSON_CALLBACK'}}
}
);
});
controller
The controller adds the received data to the scope
function OfferCtrl($scope, Offer) {
$scope.offer = Offer.get();
}
Now I'm facing the following problem. By the time the template is being loaded, the json request is not finished yet. This results in showing the template with an empty string for {{offer.price}}.
I would need a way to update the template with the corresponding values, once the offerServices has received a response (i.e. the offerService response is available)
Any ideas?
How about using ngCloak
Description
The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
The directive can be applied to the element, but typically a
fine-grained application is prefered in order to benefit from
progressive rendering of the browser view.