How can I generate on the fly directives in AngularJS? - angularjs

I want to be able to take an array of strings, and then create directives based upon those strings. Either element or attribute will work fine but can't seem to get it working.
<div ng-repeat="module in modules.directives">
<div {{module.directive}}></div>
</div>
<div ng-repeat="module in modules.directives">
<{{module.directive}}></{{module.directive}}>
</div>
<div ng-repeat="module in modules.directives">
<{{module.directive}} />
</div>
Can't get any of these to work. Any ideas?

You could define a directive that would proxy another directive like so
<div proxy="'ng-if'" proxy-value="'foo'"></div>
<div ng-init="n='ng-if'; v='foo';" proxy="n" proxy-value="v"></div>
that would both be equivalent to
<div ng-if="foo"></div>
the proxy directive definition would be
app.directive('proxy', function ($parse, $injector) {
return function (scope, element, attrs) {
var nameGetter = $parse(attrs.proxy);
var name = nameGetter(scope);
var value = undefined;
if (attrs.proxyValue) {
var valueGetter = $parse(attrs.proxyValue);
value = valueGetter(scope);
}
var directive = $injector.get(name + 'Directive')[0];
if (value !== undefined) {
attrs[name] = value;
}
return directive.compile(element, attrs, null)(scope, element, attrs);
};
});
This is actually kind of a fun directive to write once in a life. :-) but it lacks a lot of the native directive features (for instance template, templateUrl, controller, etc). All those features are available in the original Angular source code in a private function called applyDirectivesToNode, so it is easy to copy/paste some parts, but ugly... I have written a demo matching your usecase here.
Another solution, if you don't mind your proxied directive does not share the same element as the proxy directive's one, would be to $compile a dynamic template at runtime that you would include. Here is a demo.

ng-include can help you. The approach would be to define a template for each of the directives. Something like this
<script type="text/ng-template" class="template" id="test-module">
<test-module></test-module>
</script>
Then in ng-repeat do
<div ng-repeat="module in modules.directives">
<ng-include src="module.directive">
</div
If the template id matches with module.directive that directive would get rendered.

Related

Angular JS: Directive content is not available in Java script function

I have a div which has ng-click. When I click on that div, it calls a function which gets script content from a Directive and I append that to another div and access the content of the script. But when I retrieve the content of the directive I am getting directive name not the content. I want to get the content.
The function I call:
$scope.someFunction = function(){
var appendHtml = $compile("<my-custom-directive></my-custom-directive>")($scope);
$("#someId").append(appendHtml)
//But when i append I am seeing as <my-custom-directive></my-custom-directive> in html not the actual content
$(""#someId"").find('script')
}
Directive:
app.directive('myCustomDirective', function ($compile) {
return {
restrict: 'E',
templateUrl: '/somecontent.html',
replace: true,
link: function ($scope, elem, attr, ctrl) {}
};
});
Somecontent.html
<script type="text/template">
<div class="arrow" style="left: 50%;"></div>
some elements here
</div>
</script>
The HTML where I call from:
<div ng-click="someFunction()">
<div id="someId">
<my-custom-directive></my-custom-directive>
//But Here I am seeing this, when calling
$(appendHtml).find('script') in my javascript function, after Javasciprt function call is done, It works fine. But i want to see actual content here when calling $(""#someId"").find('script')
<div>
</div>
it is not a good practice.
you can use ng-if and binding instead , like the follwing:
HTML
<div ng-click="someFunction()">
<div id="someId">
<div ng-if="$scope.isVisible">
<my-custom-directive></my-custom-directive>
</div>
//But Here I am seeing this, when calling
$(appendHtml).find('script') in my javascript function, after Javasciprt function call is done, It works fine. But i want to see actual content here when calling $(""#someId"").find('script')
<div>
</div>
controller:
$scope.isVisible = false;
$scope.someFunction = function(){
$scope.isVisible = true;
}
you can also pass isolate scope param to your directive and check the param in the directive template
It's possible that you're just not using jQuery or jqLite to select elements correctly.
Your someFunction might need to look more like this:
vm.someFunction = function () {
var appendHtml = $compile('<my-custom-directive></my-custom-directive')($scope);
angular.element(document).find('some-id-element').append(appendHtml);
};
I put together this plunk that I think might achieve what you're trying to do.
Does this approximate your goal?

Using KendoUI methods in AngularJS

How can I use the methods provided by KendoUI widgets in an AngularJS application?
For the configuration of the widgets the directive attributes are used within the markup via k- prefixes, I know.
But in the documentation, there are a lot of methods assigned to the widgets.
For example the kendo-mobile-tab-strip widget has a switchTo method.
The example shows to use it this way (without angular)
var app = new kendo.mobile.Application();
function onClick() {
var tabstrip = app.view().footer.find(".km-tabstrip").data("kendoMobileTabStrip");
tabstrip.switchTo("#bar"); //activate "bar" tab
}
How can I access the app variable in an Angular controller, when I just use a <kendo-mobile-application> directive to initialize the app?
Is there any other (proper) way of using the widget methods?
I'm curious of the best practice here, because this all feels not like Angular...
When you supply a value to the widget's attribute (or to a k-scope-field attribute), that becomes the name of the scope property which you can use to get a reference to the widget.
HTML
<div ng-controller="MainCtrl">
<div kendo-grid="myGrid"></div>
<!-- or -->
<div kendo-grid k-scope-field="myGrid"></div>
</div>
Controller
angular.controller("MainCtrl", function($scope) {
// the widget is accessible from the scope
$scope.myGrid.doSomething();
});
You're right, calling methods on widget isn't like Angular. So in most cases you want to be putting this in your own directives, wrapping kendo's widgets. This lets you keep DOM specific code out of your application controllers and contained in directives, where they belong.
HTML
<div ng-controller="MainCtrl">
<my-grid-directive></my-grid-directive>
</div>
Directive
angular.controller("myGridDirective", function() {
return {
template: "<div kendo-grid='myGrid'></div>",
link: function (scope, element, attrs) {
scope.myGrid.doSomething();
}
};
});
I never using kendo mobile application so it might be different but in general to access the kendo object in the $scope object by adding identifier in kendo attribute per example below.
<div ng-app="app" ng-controller="MyCtrl">
<input kendo-datepicker="datePicker" k-on-change="onChange()">
</div>
<script>
angular.module("app", [ "kendo.directives" ]).controller("MyCtrl", function($scope) {
$scope.onChange = function() {
alert($scope.datePicker.value());
};
});
</script>
http://docs.telerik.com/kendo-ui/AngularJS/introduction#getting-widget-references

How can I use ng-repeat to generate custom html tags or directive?

I am trying to create some custom tag using angularjs with ng-repeat but its not working, its giving tag name in double quotation marks.
Good result is: shouldn't show <div> or another tags.
I have a demo version here : http://plnkr.co/edit/aT2UjMIGGLvnCGFxXC3a?p=preview
or you can use code snippet
my code is:
angular.module('myApp', [])
.controller('MainCtrl', function($scope) {
$scope.items = [
{
"name":"directive1"
},
{
"name":"directive2"
},
{
"name":"div"
}
];
})
.directive("showitems", function(){
return {
restrict: "E",
template: '<div class="item" ng-repeat="item in items"><div class="item-title">{{item.name}}</div><div class="item-body"><{{item.name}}></{{item.name}}></div></div>'
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<showitems></showitems>
</div>
AFAIK, doing something like <{{item.name}}></{{item.name}}> will never work in angular since the markups aren't treated as new DOM elements. A much better, and more manageable approach is to write directives for all possible types you'd want to render as DOM elements.
Also, to know more about DOM manipulations from within a directive, read up about $compile: here
An alternate way of doing this would be something like this in your directive template:
<directive1 ng-if="directiveType == 'directive1'"></directive1>
<directive2 ng-if="directiveType == 'directive1'"></directive2>
<directive3 ng-if="directiveType == 'directive1'"></directive3>
And in your controller/directive, you must declare directiveType to the type of directive you want to render.
Instead of wrapping {{item.name}} into its own tag, make a customTag directive. This can take a string (the tag name) and create and compile an element with it.
.directive("customTag", function ($compile) {
return {
restrict: "A",
link: function (scope, element, attrs) {
//pass customTag in as part of the directive attribute
var customTag = attrs.customTag;
if (!customTag) return;
var customElem = angular.element("<" + customTag + "></" + customTag + ">");
//replace your custom element directive with the compiled one
element.replaceWith($compile(customElem)(scope));
}
}
}
Then in your template.html:
<div class="item" ng-repeat="item in items">
<div class="item-title">{{item.name}}</div>
<div class="item-body">
<!-- This will use customTag directive to create element item.name -->
<div data-custom-tag="{{item.name}}"></div>
</div>
</div>
A div tag (or any non-directive) wouldn't be too useful because it doesn't get any innerHTML or behavior like directive1 and directive2 would get. This of course is a simple example, you may want to put some more checks or restrictions in there, but this should get you started.
See fiddle here that builds on your example.

How to manipulate DOM elements with Angular

I just can't find a good source that explains to me how to manipulate DOM elements with angular:
How do I select specific elements on the page?
<span>This is a span txt1</span>
<span>This is a span txt2</span>
<span>This is a span txt3</span>
<span>This is a span txt4</span>
<p>This is a p txt 1</p>
<div class="aDiv">This is a div txt</div>
Exp: With jQuery, if we wanted to get the text from the clicked span, we could simple write:
$('span').click(function(){
var clickedSpanTxt = $(this).html();
console.log(clickedSpanTxt);
});
How do I do that in Angular?
I understand that using 'directives' is the right way to manipulate DOM and so I am trying:
var myApp = angular.module("myApp", []);
myApp.directive("drctv", function(){
return {
restrict: 'E',
scope: {},
link: function(scope, element, attrs){
var c = element('p');
c.addClass('box');
}
};
});
html:
<drctv>
<div class="txt">This is a div Txt</div>
<p>This is a p Txt</p>
<span>This is a span Txt </span>
</drctv>
How do I select only 'p' element here in 'drctv'?
Since element is a jQuery-lite element (or a jQuery element if you've included the jQuery library in your app), you can use the find method to get all the paragraphs inside : element.find('p')
To Answer your first question, in Angular you can hook into click events with the build in directive ng-click. So each of your span elements would have an ng-click attribute that calls your click function:
<span ng-click="myHandler()">This is a span txt1</span>
<span ng-click="myHandler()">This is a span txt2</span>
<span ng-click="myHandler()">This is a span txt3</span>
<span ng-click="myHandler()">This is a span txt4</span>
However, that's not very nice, as there is a lot of repetition, so you'd probably move on to another Angular directive, ng-repeat to handle repeating your span elements. Now your html looks like this:
<span ng-repeat="elem in myCollection" ng-click="myHandler($index)">This is a span txt{{$index+1}}</span>
For the second part of your question, I could probably offer an 'Angular' way of doing things if we knew what it was you wanted to do with the 'p' element - otherwise you can still perform jQuery selections using jQuery lite that Angular provides (See Jamie Dixon's answer).
If you use Angular in the way it was intended to be used, you will likely find you have no need to use jQuery directly!
You should avoid DOM manipulation in the first place. AngularJS is an MVC framework. You get data from the model, not from the view. Your example would look like this in AngularJS:
controller:
// this, in reality, typically come from the backend
$scope.spans = [
{
text: 'this is a span'
},
{
text: 'this is a span'
},
{
text: 'this is a span'
}
];
$scope.clickSpan = function(span) {
console.log(span.text);
}
view:
<span ng=repeat="span in spans" ng-click="clickSpan(span)">{{ span.text }}</span>
ng-click is the simpler solution for that, as long as I do not really understand what you want to do I will only try to explain how to perform the same thing as the one you have shown with jquery.
So, to display the content of the item which as been clicked, you can use ng-click directive and ask for the event object through the $event parameter, see https://docs.angularjs.org/api/ng/directive/ngClick
so here is the html:
<div ng-controller="foo">
<span ng-click="display($event)" >This is a span txt1</span>
<span ng-click="display($event)" >This is a span txt2</span>
<span ng-click="display($event)" >This is a span txt3</span>
<span ng-click="display($event)" >This is a span txt4</span>
<p>This is a p txt 1</p>
<div class="aDiv">This is a div txt</div>
</div>
and here is the javascript
var myApp = angular.module("myApp", []);
myApp.controller(['$scope', function($scope) {
$scope.display = function (event) {
console.log(event.srcElement.innerHtml);
//if you prefer having the angular wrapping around the element
var elt = angular.element(event.srcElement);
console.log(elt.html());
}
}]);
If you want to dig further in angular here is a simplification of what ng-click do
.directive('myNgClick', ['$parse', function ($parse) {
return {
link: function (scope, elt, attr) {
/*
Gets the function you have passed to ng-click directive, for us it is
display
Parse returns a function which has a context and extra params which
overrides the context
*/
var fn = $parse(attr['myNgClick']);
/*
here you bind on click event you can look at the documentation
https://docs.angularjs.org/api/ng/function/angular.element
*/
elt.on('click', function (event) {
//callback is here for the explanation
var callback = function () {
/*
Here fn will do the following, it will call the display function
and fill the arguments with the elements found in the scope (if
possible), the second argument will override the $event attribute in
the scope and provide the event element of the click
*/
fn(scope, {$event: event});
}
//$apply force angular to run a digest cycle in order to propagate the
//changes
scope.$apply(callback);
});
}
}
}]);
plunkr here: http://plnkr.co/edit/MI3qRtEkGSW7l6EsvZQV?p=preview
if you want to test things

Ng-controller on same element as ng-repeat - no two-way-data-binding

I can't get two-way-data-binding to work in an Angular js ng-repeat.
I have an ng-controller specified on the same element that has the ng-repeat specified -
I just learnt that by doing this, I can get a hold of each item that is being iterated over by ng-repeat. Here is the HTML:
<div ng-controller="OtherController">
<div id="hoverMe" ng-controller="ItemController" ng-mouseenter="addCaption()"
ng-mouseleave="saveCaption()" ng-repeat="image in images">
<div class="imgMarker" style="{{image.css}}">
<div ng-show="image.captionExists" class="carousel-caption">
<p class="lead" contenteditable="true">{{image.caption}}</p>
</div>
</div>
</div>
</div>
And here is the ItemController:
function ItemController($scope){
$scope.addCaption = function(){
if($scope.image.captionExists === false){
$scope.image.captionExists = true;
}
}
$scope.saveCaption = function(){
console.log($scope.image.caption);
}
}
And the OtherController:
function OtherController($scope){
$scope.images = ..
}
When I hover the mouse over the #hoverMe-div - the caption-div is added correctly. But when I input some text in the paragraph and then move the mouse away from the #hoveMe-div, the $scope.image-variables caption value is not updated in the saveCaption-method. I understand I'm missing something. But what is it?
You don't need a ng-controller specified on the same element that has the ng-repeat to be able to get each item.
You can get the item like this:
<div ng-repeat="image in images" ng-mouseenter="addCaption(image)" ng-mouseleave="saveCaption(image)" class="image">
And in your controller code:
$scope.addCaption = function (image) {
if(!image.captionExists){
image.captionExists = true;
}
};
To get contenteditable to work you need to use ng-model and a directive that updates the model correctly.
Here is a simple example based on the documentation:
app.directive('contenteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, controller) {
element.on('blur', function() {
scope.$apply(function() {
controller.$setViewValue(element.html());
});
});
controller.$render = function(value) {
element.html(value);
};
}
};
});
Note that the directive probably needs more logic to be able to handle for example line breaks.
Here is a working Plunker: http://plnkr.co/edit/0L3NKS?p=preview
I assume you are editing the content in p contenteditable and are expecting that the model image.caption is update. To make it work you need to setup 2 way binding.
2 way binding is available for element that support ng-model or else data needs to be synced manually. Check the ngModelController documentation and the sample available there. It should serve your purpose.

Resources