Angular nested directive only the first child is rendered [duplicate] - javascript

This question already has answers here:
AngularJS - html under my directive is not showing
(2 answers)
Closed 6 years ago.
I have a parent directive which includes two children directives, first and second. I noticed that only the first child is rendered. Also, if I put some arbitrary HTML markup before the first one, it's all rendered but if I put them after that then they will not show up. Why is this?
See the jsfiddle:
<!-- index.html -->
<div ng-app="myApp">
<my-parent-dir />
</div>
<!-- main.js -->
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child /> <my-second-child />'
};
});
app.directive("myFirstChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="first">',
};
});
app.directive("mySecondChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="second">',
};
});

Try to use it like this:
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
};
});
From the angular issues in github:
self-closing or void elements as the html spec defines them are very
special to the browser parser. you can't make your own, so for your
custom elements you have to stick to non-void elements ().
this can't be changed in angular.

Self defined tags are no leaf tags so you will have to use:
template:'<my-first-child></my-first-child> <my-second-child></my-second-child>'

Since you're using custom tags, you need to close the tag, because HTML spec does not allow self closing tags.
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
JSFiddle

Related

Angular model looses scope with ng-included in directive [duplicate]

This question already has answers here:
Losing scope when using ng-include
(4 answers)
Closed 8 years ago.
I wasn't sure on how to properly title this question, so here it goes ..
I'm trying to setup a dynamic way to change out templates inside of a directive using the ng-include method. I've set up two Plunker examples and although one should work just like the other, that doesn't seem to be the case.
HTML for both Examples:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<main></main>
</body>
</html>
Example #1:
http://plnkr.co/edit/bi3Plrm8xufuN79Nvajj?p=preview
I'm setting up two directives (one main, and one nested as a child):
angular.module('myApp', ['Test']);
angular.module('Test', [])
.directive('main', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel"><br><br><child></child>'
};
}
])
.directive('child', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel">'
};
}
]);
Easy. When running the app both fields populate respectively as the model changes.
Example #2:
http://plnkr.co/edit/3ajcTyfJElEzbqvsWwBM?p=preview
The HTML stays the same, but the js is a little different:
angular.module('myApp', ['Test']);
angular.module('Test', [])
.directive('main', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel"><br><br><child></child>'
};
}
])
.directive('child', [
function () {
return {
restrict: 'E',
controller: function($scope) {
$scope.myTemplate = 'test-template.html'
},
template: "<div ng-include='myTemplate'></div>"
};
}
]);
test-template.html:
<input type="text" ng-model="myModel">
This time, if I interact with the first input that is generated, both inputs update respectively as they should. Here's when it gets interesting ... When/if I interact with the second input (the one generated by ng-include) I loose all binding. Forever... Almost as if it's created its own version of the model. Afterwards, changing the first input has no effect on the second.
What is happening here? Is it indeed creating a new instance of myModel? And if so, how can this be avoided when using this ng-include method?
This is no weird, as PSL said,
ng-include creates new scope.
If you want to create the behaviour that keeps those models attached,
You should change
<input type="text" ng-model="myModel">
To:
<input type="text" ng-model="$parent.myModel">

Dynamically Create and Load Angular Directive

In my application i have a list of custom directive names.
$scope.data =["app-hello","app-goodby","app-goodafter"];
each name in this array is one directive that im created.
var app = angular.module('app',[]).controller('mainCtrl',function($scope){
$scope.data =["app-hello","app-goodby","app-goodafter"];
}).directive('appHello',function(){
return {
restrict:'EA',
template:'<h1>Hello Directive</h1>'
};
}).directive('appGoodbye',function(){
return {
restrict:'EA',
template:'<h1>GoodBye</h1>'
};
}).directive('appGoodafter',function(){
return{
restrict:'EA',
template:'<h1>Good Afternoon</h1>'
};
});
now i want to load directive with ng-repeat in the view for example because i used EA restrict for directive can create directive in ng-repeat like this :
<div ng-repeat="d in data" >
<div {{d}}></div>
</div>
but this way it doesn't work. so the real question is if i have list of directive how to load this directive with ng-repeat.for this scenario i create a jsbin .
thanks.
You need a "master" directive that $compiles the HTML (optionally containing directives) into an Angular-aware template and then links the compiled element to a $scope:
app.directive('master', function ($compile) {
return {
restrict: 'A',
link: function postLink(scope, elem, attrs) {
attrs.$observe('directive', function (dirName) {
if (dirName) {
var compiledAndLinkedElem =
$compile('<div ' + dirName + '></div>')(scope);
elem.html('').append(compiledAndLinkedElem);
}
});
}
};
});
<div master directive="{{dir}}" ng-repeat="dir in ['dir1', 'dir2', 'dir3']"></div>
See, also, this short demo.
You can do it in this way:
Directive:
app.directive('compile',function($compile){
return{
restrict:'A',
template: '<div></div>',
link:function(scope,elem,attrs){
scope.name = attrs.compile;
elem.children('div').attr(scope.name,'');
$compile(elem.contents())(scope);
}
};
});
HTML:
<div ng-repeat="d in data" compile="{{d}}">
</div>
Jsbin: http://jsbin.com/wofituye/4/edit
I actually prefer to create templates, that just contain the directive. Then you can use ng-include this then enables you to easily pass scope variables into the dynamically chosen directives too.
Here is my widget code fore example:
<div ng-repeat="widget in widgets track by $index" ng-include="widget.url" class="widget-container" ng-class="widget.widget_type.config.height +' ' + widget.widget_type.config.width">
</div>
Then I set the widget.url to a template containing just the right directive.
I can then in my directive do this:
<custom-widget ng-attr-widget="widget"></custom-widget>
Then I have access to the dynamic variable too, so I can access configuration specifics too, without having to dynamically generate HTML strings and compile them. Not a perfect solution, but personally I used to use the other approach mentioned, and discovered that this fit my needs much better.

Using directives in AngularJS

I'm trying to learn AngularJS and have one question/concept I'm struggling to understand.
Take the following demo code I created:
js
var app = angular.module('customerPortalApp', ["ui.router"]);
app.config( function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /route1
$urlRouterProvider.otherwise("/route1");
$stateProvider
.state('route1', {
url: "/route1",
templateUrl: "/static/html/partials/_campaign_title.html",
controller: "CampaignCtrl"
})
});
app.controller("CampaignCtrl", function($scope){
$scope.loadCampaign = function(){
alert("loaded campaign!")
}
});
app.directive("campaign", function() {
var MessageBox = angular.element('<div class="alert alert-success">hello</div>');
var link = function (scope, element){
scope.loadCampaign();
}
return {
restrict: "E",
compile: function (template){
template.append(MessageBox)
return link
}
}
});
html
<div class="container" ng-app="customerPortalApp">
<div class="row">
<div class="span12">
<div class="well" ui-view></div>
</div>
</div>
<div ng-controller="CampaignCtrl">
<campaign>
test
</campaign>
</div>
</div>
Looking at this code I call the controller in my config and the new $stateProvider I added now takes care of the template loading, so why do I now need directive? In my example I don't now know why I would need one, can ui-view be used to house more controllers?
For your example, you can to use a ui-view. In general, I used directives for a reusable and specified behavior.
What are Directives?
At a high level, directives are markers on a DOM element (such as an attribute, element name, 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 ngView. Much like you create controllers and services, you can create your own directives for Angular to use. When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements.
See the documentation: Angular JS Documentation
A example below as I used the directives:
/* Get the boolean data and switch true or false for respective images. This Example use the bootstrap to show images */
App.directive('bool2image', function() {
return {
restrict: 'C',
replace: true,
transclude: true,
scope: { boolean: '#boolean' },
template: '<div ng-switch on="boolean">' +
'<div ng-switch-when="false"><span><i class=icon-remove></i></span></div>' +
'<div ng-switch-when="true"><span><i class=icon-ok></i></span></div>' +
'</div>'
}
});
So, to used the directive called into the code:
<div class="bool2image" boolean="{{booleanData}}"></div>
I hope to help you.

Better granularity in Angular directive restriction

Is there a way to write a directive that just applies to a specific element + attribute + attribute value?
My very first intention would be to have separate directives, for modularity and maintenance purposes, but I'm afraid that's not possible as I get an error from Angular telling me there are multiple directives matching the element.
So my scenario is as follows: I want to write my own input elements, e.g.
<input type="time-picker">
<input type="date-picker">
so I did
app.directive('input', function () {
return {
restrict: 'E',
templateUrl: function ($element, $attrs) {
if ($attrs.type === 'date-picker' || $attrs.type === 'time-picker') {
return $attrs.type + '.html';
}
},
controller: function ($scope, $element, $attrs) {
if ($attrs.type === 'date-picker') {
console.log('date-picker');
}
else if($attrs.type === 'time-picker') {
console.log('time-picker');
}
}
}
});
This works well as long as there are no other input elements in the page.
If I put
<input type="time-picker">
<input type="date-picker">
it works fine. Now if I add
<input type="text">
the whole page hangs.
See my fiddle here: http://jsfiddle.net/pWc3K/8/
If you change your html to this:
<input type="text" time-picker> <input type="text" date-picker>
Then you could wire up your directives based on those attributes like so:
app.directive('timePicker', function(){
return {
restrict: 'A',
...
}
});
app.directive('datePicker', function(){
return {
restrict: 'A',
...
}
});
Putting time-picker/date-picker in an input type for an element isn't really valid. If you read up on the angular docs for directives you'll find a whole list of the different things you can associate on. The four ones are:
E - Element name: <my-directive></my-directive>
A - Attribute: <div my-directive="exp"> </div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->
Try and understand In AngularJs custom directives created can have following restrictions:
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
‘M’ – only comments
These restrictions can all be combined as needed:
for eg:
'AEC' - matches either attribute or element or class name

how to write custom 'row' and 'col' element for angularjs

I'm trying to write a little dsl around the grid elements outlined here: http://foundation.zurb.com/docs/grid.php
basically what I wish to do is to
<row>
<column two mobile-one>{{myText}}</col>
<column four centered mobile-three><input type="text" ng-model="myText"></input></col>
</row>
transform into:
<div class="row">
<div class="columns two mobile-one">{{myText}}</div>
<div class= "columns four centered mobile-three"><input type="text" ng-model="myText"></input></div>
</div>
Ideally, I wish to write something that can take arbitrary nesting: row -> col -> row -> col -> row.....
I am having trouble getting the first step right - nesting the elements because I can't quite figure how how to get the child elements into another template without seriously compromising the compilation process.
var app = angular.module('lapis', []);
app.directive('row', function(){
return {
restrict: 'E',
compile: function(tElement, attrs) {
var content = tElement.children();
tElement.replaceWith(
$('', {class: 'row',}).append(content));
}
}
});
just does not do anything. The failed attempt is shown here - http://jsfiddle.net/ZVuRQ/
Please help!
I was hoping not to use ng-transclude because I found that it added an additional scope.
Here is a directive that does not use ng-transclude:
app.directive('row', function() {
return {
restrict: 'E',
compile: function(tElement, attrs) {
var content = angular.element('<div class="row"></div>')
content.append(tElement.children());
tElement.replaceWith(content);
}
}
});
You may want to use tElement.contents() instead of tElement.children().
You don't need jquery at all in your example (but you need to include it on your page/jsFiddle):
var app = angular.module('lapis', []);
app.directive('row', function(){
return {
restrict: 'E',
template: '<div class="row" ng-transclude></div>',
transclude: true,
replace: true
};
});

Categories

Resources