http://codepen.io/pondnetic/pen/qdxGVV
I have a javascript string of a few lines of html displayed in my ionic app with
<div ng-bind-html="strVar | to_trusted"></div>
to_trusted is a simple filter using $sce
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}])
As shown in the codepen, ng-show and ng-hide do not function when displaying html this way. How can I get this to function as intended?
The current problem that you're experiencing is that Angular is taking your text as HTML correct, as you wanted to, but after doing so, it does not bind to the new attributes and classes as if it were an Angular template.
This behavior is not a deficiency in Angular's design, but rather a prevention that a particular directive could not turn the site completely unresponsive. Think if it needed to reevaluate each new piece of generated HTML for new directives and bindings, it would very likely enter into a never ending loop of execution and checking.
Do not use dynamic templates
The first approach to solving your problem is changing the approach: do not use dynamic templates. It is very likely that the reason you want to do that is to allow for user input to generate the template (which becomes a security concern, a potential entry for XSS), or that a third party system is generating the HTML for you, which is also a bad idea because of the separation of concerns from the systems (if your system is supposed to generate the HTML, it should not mix up other sources, much less trust them).
Use directives
If the reason you're into inserting HTML is for the purpose of reusing the HTML, what you may be looking for are directives, an Angular component that allows you to do exactly that: reuse and isolate behavior that is very tightly coupled to the generated HTML.
Here's a quick example from the directive documentation:
Dynamic templating
If you really want to go into dynamic templating, there's a way to do it. You would create a directive in the very same way as the example above, but rather than hardcoding the template into the directive (or into a template file), you can dynamically feed the contents of the template into the link function and compile it yourself with the scope provided, like so:
angular.module('variableDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer1 = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
$scope.customer2 = {
name: 'Joseph',
address: '123 Fake St'
};
}])
.directive('myCustomer', function($compile) {
var getTemplate = function(attrs) {
var isVip = attrs.type === "vip";
return isVip
? "(VIP) Name: {{customer.name}} -- (VIP address hidden)"
: "Name: {{customer.name}} -- Address: {{customer.address}}"
};
var linker = function(scope, element, attrs) {
var template = getTemplate(attrs);
element.html(template);
$compile(element.contents())(scope);
};
return {
restrict: 'E',
link: linker,
scope: {
customer: '=info'
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="variableDirective">
<div ng-controller="Controller">
<my-customer type="regular" info="customer1"></my-customer>
<br />
<my-customer type="vip" info="customer2"></my-customer>
</div>
</div>
Note however this is necessary because the compilation of the template happens after the directive has been instantiated, so you don't get access to the scope or the attributes at the moment of the directive definition.
A more detailed explanation and a reusable approach (the one I based my example on) you can find here: http://onehungrymind.com/angularjs-dynamic-templates/
Related
I'm fairly new to angularjs. Im trying to understand why it's better to use this directive, compared to just using the controller. Both examples output the same value.
Directive Example:
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
Markup:
<div ng-controller="Controller">
<div my-customer></div>
</div>
Controller Example:
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
Markup:
<div ng-controller="Controller">
Name: {{customer.name}} Address: {{customer.address}}
</div>
Maybe I just don't fully understand directives either.
At work, we use a simple exercise to see if we need a directive or not.
If a certain snippet is used more than once, we turn it into a directive.
A directive also gives a chance to add less clutter to your templates.
angular.module('DocsSimpleDirective', [])
.controller('DocsController', [function() {
this.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
scope: true
restrict: 'EA',
controller: 'DocsController',
controllerAs: 'docsCtrls',
templateUrl: 'assets/template/my-customer.directive.html'
};
})
;
would allow your template to be defined as:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Angular test</title>
</head>
<body ng-app="DocsSimpleDirective">
<my-customer></my-customer>
</body>
</html>
and your directive as:
<article>
<strong>{{ docsCtrls.customer.name }}</strong><br>
{{ docsCtrls.customer.address }}
</article>
Personally, I try to refrain from using $scope to bind data to. If somebody else starts to read your code, a magical customer, defined in some controller somewhere on the scope is a lot harder to identify than a variable on a certain controller.
Isolating your $scope can be useful (by defining scope: true) to use a a default value. If you need to stop isolating your directives, it should be something you thought about, not because it's the default value.
When you don't isolate a scope it inherits all values that are defined in the $parentScope this is useful when nesting directives, where all directives should know which parent they originate from. This has a very distinct danger, you could manipulate data in the parentscope that shouldn't be manipulated.
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
Other thing you need to set scope:true
.directive('myCustomer', function() {
return {
scope:true,
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
REFER THIS DOC
There are multiple possible ways of how to bind functionality to template, some are better than others.
BAD - use ng-controller attribute directly in html template to
bind controller function to it
BETTER - use ngRoute or ui-router to specify routes/states or your application and there you can specify controller and template per route/ state
BEST - use directive definiton object where you can again specify both controller and template to bind them together and then use directive inside of templates and routes.
The third example is than flexible in such a way that you can use directive just in any other template like <div my-directive></div> but also in any router as a inline template like: template: '<div my-directive></div>'
The third approach is the best because it's going in direction of components which are the future (because of React, Angular 2.0 and Webcomponents). I wrote a blog post and created a sample github repository ilustrating these concepts.
Controller:
A Controller is used to augment the Angular Scope.
When a Controller is attached to the DOM via the ng-controller directive, Angular will instantiate a new Controller object, using the specified Controller's constructor function.
controllers use to:
Set up the initial state of the $scope object.
Add behavior to the $scope object.
Do not use controllers to:
Manipulate DOM — Controllers should contain only business logic.
Format input — Use angular form controls instead.
Filter output — Use angular filters instead.
Share code or state across controllers — Use angular services instead.
Directives:
At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children.
Angular comes with a set of these directives built-in, like ngBind, ngModel, and ngClass.
you can create your own directives for Angular to use.
Generally directives are used to:
inject or wrapping existing thing.
Reuse same things in different palce
can inject in DOM element as attribute, element, comment or class
so if you need to reuse the same dom or logic in different place then you should use directive instead of controller.
Also you can access parent controller object from inner directive
Like:
<div data-ng-controller="mainController">
<p>hello world</p>
<some-directive></some-directive> // inject directive as element
</div>
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
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'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
I've got a tiny problem with an angular directive that's now working and I don't know why. I think it's a fairly simple issue that I'm overlooking, maybe you can help me out.
Directive is defined like this:
angular.module('directives', [])
.directive('my-directive', function () {
return {
restrict: 'AE',
scope: {
name: '=name'
},
template: '<h1>{{name}}</h1>'
};
});
Then index.cshtml:
<my-directive name="test"></my-directive>
Application.js:
var app = angular.module('MyApp', [
...,
'directives'
]);
And here's controllers.js
angular.module('controllers', ['apiServices', 'directives'])
.controller('homecontroller', function($scope, $resource, webApiService, $log, $translate, $localStorage, $sessionStorage) {
Ok confirmed that directives.js is loaded, otherwise application.js nags about 'unknown module'. There are no error messages in the console, the thing just doesn't show. Any ideas?
EDIT
So as pointed out, I changed the directive name to camelCase, but still no luck:
<my-directive name="John Doe"></my-directive>
And
.directive('myDirective', function () {
But nothing is showing yet.
EDIT
Problem is that angular expects an object to be passed into the attribute, not a string literal. If you create an object person = { name: 'John' }, pass the person in, then write {{ person.name }} ( assuming we named the attribute person + scope var person too ).
During normalization, Angular converts - delimited name to camelCase.
So use camelCase while specifying the directive inside JS:
.directive('myDirective', function () {
Fiddle
I'm sure you've figured this out already, but if you change your scope definition for name to be
scope: {
name: '#'
}
you will then be able to pass a string. The '#' interpolates the attribute while the '=' binds it. Additionally, you don't need to include an attribute name if it is the same as the scope variable.
The problem appears to be in the directive definition. You note in your question that Angular expects an object; this is true for the "=" scope, but not for the "#" scope. In the "#" scope, Angular expects a string only. I have created a snippet below.
Too many modules
Unless you are reusing the directive in multiple applications, do not create a new module for it. Add the directive definition to the module that you created for the application. In my example below, I called the module back by using "angular.module( moduleName )"... When only one argument is used, Angular returns the existing object rather than creating a new one. This is how we can separate the code into many files.
Things to Note
You will notice the following:
You do not need to load the module into the app variable. Calling the Singleton each time is actually safer and easier on memory management.
The directive is in camel case, as you have already noted.
I am setting the name attribute to a string value and not an object; this works because of the "#" scope setting.
The div is set to ng-app='MyApp'. This usually is set to the html element, but I did not want to mess with the DOM on Stack Exchange. The ng-app directive can be set on any element, and the directives associated with that module will be applied on all elements that are within that element's scope. Without the ng-app directive, Angular does not know which module to run on the page.
//app.js - this defines the module, it uses two parameters to tell the injector what to do.
angular.module('MyApp',[]);
//directive.js stored elsewhere
//this calls back the module that has been created. It uses one parameter because the injector is no longer needed.
angular.module('MyApp').directive('myDirective', function () {
return {
restrict: 'AE',
scope: {
name: '#'
},
template: '<h1>{{name}}</h1>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="test"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
</div>
Using Injection
When you have a different module for each and every directive or controller, each one must be injected into the application's module definition. This leaves a lot of room for error. As a best practice, only create a new module when necessary, and make the module a container for a group of related functionality and not a single item.
The code below demonstrates proper injection.
angular.module( "MyApp", ['ReusableDirectives'] );
angular.module( "MyApp" ).directive( "myDirective", function(){
return {
restrict: "AE",
scope: { name: "#" },
template: "<p>This is the directive I defined in the example above. It uses <u>the same module</u> as my main application, because it is not going to be reused over and over again. In fact, I will need it just for this application, so I don't need to complicate things with a new module. This directive takes an attribute called 'name' and if it is a string allows me to manipulate the string within my templates scope to do things like this: {{'hello ' + name + '!'}}</p>"
};
} );
angular.module( "ReusableDirectives", [] );
angular.module( "ReusableDirectives" ).directive("reusableDirective", function(){
return {
restrict: "E",
template: "<p>This is a directive that I intend to use in many, many applications. Because I will reuse it so much, I am putting it in a separate module from my main application, and I will inject this directive. This is the only reason that this directive is not in the same module as the one I defined above.</p>"
};
} ).directive("reusableDirective2", function(){
return {
restrict: "E",
template: "<p>This is a second directive that I intend to use in multiple applications. I have stored it in a module with the first directive so that I can freely inject it into as many apps as I like.</p>"
};
} )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="Johnny"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
<h3>Injected Directives</h3>
<reusable-directive></reusable-directive>
<reusable-directive2></reusable-directive2>
</div>
Keep it simple. Define your directives on a single module for your application. Once you have that done and working, if you need the directives again in another application, refactor and experiment with injections at that time after you have some more Angular practice under your belt.
You have a bright future with Angular, keep up the good work!
Your directive must be camel-cased
.directive('myDirective', function () {
then in your html, your are free whether to call it my-directive or myDirective
Both are valid
<my-directive name="test"></my-directive>
<myDirective name="test"></myDirective>
Just to follow up on this, I had to use the following way to get my directive to work.
<my-directive name="test"></my-directive>