multi element directive on different DOM nodes - angularjs

For example, I have next html:
<header myDirective> <!-- 1 -->
...
</header>
<div menu>
...
</div>
... <!-- here are some html -->
<section>
<div myDirective> <!-- 2 -->
...
</div>
</section>
I want that myDirective with comment and myDirective with comment which are set on different DOM nodes, were combined. I mean I want that inside
directive link function I would get param "element" which would contain both of these DOM nodes. I know about property multiElement, but it works only on siblings if I understood
correctly this property. How can I get what I want? For example, I can have even such html(numbers in comments show which DOM nodes must be combined):
<header myDirective> <!-- 1 -->
<div myDirective> <!-- 2 -->
...
</div>
<div myDirective> <!-- 2 -->
...
</div>
</header>
<div myDirective> <!-- 3 -->
...
</div>
<div myDirective> <!-- 3 -->
...
</div>
<div myDirective> <!-- 1 -->
...
</div>

Smth like this is done by introducing some top-level parent directive in combine with several child directives. Child directives require parent one so they can use parent controller.
html:
<div main>
<div mydir><div><div mydir>1
</div></div></div>
<span mydir>2
</span>
Directive:
app.directive('mydir', function() {
return {
require : '^main',
link : function(scope, el, attrs, mainCtrl) {
mainCtrl.register(el);
}
};
});
http://plnkr.co/edit/znFa6lGUGmQ0bYw097zH?p=preview

Related

How do I not add any additional containers when using AngularJS 1.6 component?

I have a simple component site-header in Angular 1.6 (code below: site-header.js and site-header.html). I use it in my markup as element <site-header></site-header> (code below: app.html).
The problem I'm having is I would like to get rid of <site-header> element container, because that unnecessarily creates 2 containers for my header (code: resulting HTML) - <site-header> and <header> - while I only need 1. I like to have my markup clean and don't want any excessive containers. They may also force me to write additional styles. I'd like to get rid of one of them (code: desired HTML).
I tried stripping wrapping <header> from site-header.html but then Angular gave me error:
XML Parsing Error: junk after document element
Location: file:///home/robert/programming/e-lektury/e-lektury-web/src/main/webapp/dist/app/components/site-header/site-header.html
Line Number 6, Column 3:
Main question
Could you somehow get rid of 1 of the containers - reduce the number of wrapping elements to 1?
Additional question
Why is Angular forcing another container wrapping component's template, when you already have a wrapping element, which is a component's declaration (<component-name>)?
site-header.js
angular
.module('site-header', [])
.component('site-header', {
controller: function() {
this.pageTitle = "Page title";
this.pageSlogan = "Page slogan";
},
templateUrl: './app/components/site-header/site-header.html'
}
site-header.html
<header>
<section>
<h1>{{ $ctrl.pageTitle }}</h1>
<h2>{{ $ctrl.pageSlogan }}</h2>
</section>
<section>
<!-- Login and register. -->
Login
Register
<!-- Site search. -->
<input type="search" />
</section>
</header>
app.html
<body>
<site-header></site-header>
</body>
resulting HTML
<body>
<site-header>
<header>
<section>
<h1>Page title</h1>
<h2>Page slogan</h2>
</section>
<section>
<!-- Login and register. -->
Login
Register
<!-- Site search. -->
<input type="search">
</section>
</header>
</site-header>
</body>
desired HTML
<body>
<site-header>
<!-- NO <header> here! but there could be <header> instead of
<site-header> as long as there is only one of them. -->
<section>
<h1>Page title</h1>
<h2>Page slogan</h2>
</section>
<section>
<!-- Login and register. -->
Login
Register
<!-- Site search. -->
<input type="search">
</section>
</site-header>
</body>
I would change the component to a directive so you don't have to use an element, and instead can bind to an attribute:
JS
angular
.module('site-header', [])
.directive('site-header', function() {
return {
restrict: 'A',
replace: true,
controllerAs: 'vm',
controller: function() {
this.pageTitle = "Page title";
this.pageSlogan = "Page slogan";
},
templateUrl: './app/components/site-header/site-header.html'
}
}
Main HTML
<body>
<header site-header>
</header>
</body>
Resulting HTML
Page title
Page slogan
Login
Register
<!-- Site search. -->
<input type="search">
In AngularJs 1.x you can't get rid of the component name markup in the resulting html, But you can write the HTML you want in the component template :
if site-header.html =
<section>
<h1>{{ $ctrl.pageTitle }}</h1>
<h2>{{ $ctrl.pageSlogan }}</h2>
</section>
<section>
<!-- Login and register. -->
Login
Register
<!-- Site search. -->
<input type="search" />
</section>
and Main HTML :
<body>
<site-header></site-header>
</body>
You'll have the resulting html you expect i.e.:
<body>
<site-header>
<!-- NO <header> here! but there could be <header> instead of
<site-header> as long as there is only one of them. -->
<section>
<h1>Page title</h1>
<h2>Page slogan</h2>
</section>
<section>
<!-- Login and register. -->
Login
Register
<!-- Site search. -->
<input type="search">
</section>
</site-header>
</body>

Angular wrap transcluded elements separately?

I am fairly new with Angular. And although I've made a lot of progress there are still a couple of things I don't know.
Currently I am running into a transclusion 'problem'.
Basically what we want is to wrap every transcluded element/directive seperately with html that's controlled by the parent directive.
Example:
<my:directive>
<my:sub-directive>Child 1</my:sub-directive>
<my:sub-directive>Child 2</my:sub-directive>
<my:sub-directive>Child 3</my:sub-directive>
</my:directive>
Desired result (I've left the directive elements to make the example a bit more clear):
<my:directive>
<ul>
<li>
<div class="panel">
<div class="header">
// Some stuff that's controlled by my:directive comes here
</div>
<div class="content">
<my:sub-directive>Child 1</my:sub-directive>
</div>
</div>
</li>
<li>
<div class="panel">
<div class="header">
// Some stuff that's controlled by my:directive comes here
</div>
<div class="content">
<my:sub-directive>Child 2</my:sub-directive>
</div>
</div>
</li>
<li>
<div class="panel">
<div class="header">
// Some stuff that's controlled by my:directive comes here
</div>
<div class="content">
<my:sub-directive>Child 3</my:sub-directive>
</div>
</div>
</li>
</ul>
</my:directive>
Does anyone have a clue how to handle this? I know in my example I could introduce a panel directive, but note this is just an example of the same problem.
You can pass a fifth parameter to your directive's link function transclude and then do your manipulation in there, here's a quick example:
angular.directive('myDirective', function ($compile) {
return {
restrict:'EA',
transclude:true,
link: function (scope, elm, attrs,ctrl,transclude) {
transclude(scope,function(clone) {
//clone is an array of whatever is inside your main directive
clone.filter('mySubDirective').each(function(index, value){
//create your html and you might have to $compile it before:
elm.append(myHtml);
});
});
}
};
})

angular controllers, multiple templates, and passing scope values from controllers to master page

I'm starting a new project and am going to be using angular in a "single page architecture" application. I'm a little new to angular.
So, I purchased a template for my site. It has 2 distinct layouts that I would like to use. 1 for my unauthenticated (marketing) pages and another for most of my authenticated pages.
The difference in each is subtle, but the inside pages require a class on the <body> tags that the outside pages cannot have. I considered using 2 layouts but then that got tricky as I started thinking about how I would lay out my urls.
My thought is to use angular to manage my layout so that I only need one master page like this:
<body ng-class="{menu-right-hidden: isInternalPage }">
<div class="container-fluid">
<div ng-if="isInternalPage" id="menu" class="hidden-print hidden-xs sidebar-blue sidebar-brand-primary">
<!-- sidebar content -->
</div>
<div id="content">
#RenderBody()
</div>
<div class="clearfix"></div>
<div ng-if="isInternalPage" id="footer" class="hidden-print">
<!-- internal footer -->
</div>
<div ng-if="!isInternalPage" id="footer" class="hidden-print">
<!-- external footer -->
</div>
</div>
</body>
My question is this: Is there an easy way to set isInternalPage (and possibly other valies) without having $scope.isInternalPage = true/false; decorating all of my controllers?
You could use ng-init and define a scope variable on $rootScope:
<body ng-init="$root.isInternalPage = true" ng-class="{menu-right-hidden: $root.isInternalPage }">
<div class="container-fluid">
<div ng-if="$root.isInternalPage" id="menu" class="hidden-print hidden-xs sidebar-blue sidebar-brand-primary">
<!-- sidebar content -->
</div>
<div id="content">
#RenderBody()
</div>
<div class="clearfix"></div>
<div ng-if="$root.isInternalPage" id="footer" class="hidden-print">
<!-- internal footer -->
</div>
<div ng-if="!$root.isInternalPage" id="footer" class="hidden-print">
<!-- external footer -->
</div>
</div>
</body>
Alternatively, you could assign your variable on $rootScope inside one of your controllers:
app.controller('ctrl', function($rootScope) {
$rootScope.isInternalPage = true;
});
What you can do in this case is create an AngularJS service: "You can use services to organize and share code across your app"
angular.module('core').service('GlobalVars', [ 'addDependenciesHere',
function(addDependenciesHere) {
this.isInternalPage = someVal ? true : false
}
]);
Then inject this into the constructor for each controller
angular.module('core').controller('HomeController', ['$scope', 'GlobalVars',
function ($scope, GlobalVars) {
$scope.globals = GlobalVars;
}
]);
Then in your view you can access this directly
<body ng-class="{menu-right-hidden: globals.isInternalPage }">

Scope issues when declaring controller for a modal in AngularUI/Boostrap

What I'm trying to do is have a custom directive inside a modal that just returns a list of files. The issue I'm having is that the scope seems to be different depending on how I declare my controller on my modal. Inside my modal I have a custom directive with an isolated scope that just returns a list of selected files. The first method I have is declaring it as a parameter in the modal creation.
$scope.openModal = function(){
uploadDialog = $modal.open({
templateUrl: 'modal.html',
size: 'lg',
controller:'modalController'
});
The second method I tried is declaring it at the top of the div of the modal template so I had to make a new div and wrap the whole modal template.
The second method returns everything fine, but the first method doesn't return it at all. I did notice while debugging that the "this" property has the value selectedFiles. Why does the two method yield different results?
Method 1 Plunker: http://plnkr.co/edit/6FTQq7fT49lETR5TEzaF?p=preview
Method 2 Plunker: http://plnkr.co/edit/QWnbH8GZArMgYqgcQ8L9?p=preview
To answer your question, please first see my comments in the DOM elements after a modal template has been compiled below:
Method 1:
<!-- Method 1 controller's scope is here, it is the same as modal's scope -->
<div class="modal fade in ng-isolate-scope">
<div class="modal-dialog modal-lg">
<!-- This ng-transclude create a new scope for each its children elements -->
<div class="modal-content" ng-transclude>
<div class="modal-header ng-scope">
<h3 class="modal-title">Test</h3>
</div>
<!-- The selectedFiles will be stored in this scope, not the controller scope above. -->
<div class="modal-body ng-scope">
<upload-dir files="selectedFiles" class="ng-isolate-scope">
<div>{{selectedFiles}}</div>
<button ng-click="clickHere(selectedFiles)">click here</button>
<div>From $scope: <input type="text" ng-model="test"></div>
<div>From parameter: <input type="text" ng-model="testParam"></div>
</div>
<div class="modal-footer ng-scope"></div>
</div>
</div>
</div>
Method 2:
<!-- The modal's scope is here -->
<div class="modal fade in ng-isolate-scope">
<div class="modal-dialog modal-lg">
<!-- This ng-transclude create a new scope for each its children elements -->
<div class="modal-content" ng-transclude>
<!-- Method 2 controller's scope is here -->
<div ng-controller="modalController" class="ng-scope">
<div class="modal-header">
<h3 class="modal-title">Test</h3>
</div>
<!-- There is no new scope created here, -->
<!-- so the selectedFiles will be stored in the controller's scope above -->
<div class="modal-body">
<upload-dir files="selectedFiles" class="ng-isolate-scope">
<div>{{selectedFiles}}</div>
<button ng-click="clickHere(selectedFiles)">click here</button>
<div>From $scope: <input type="text" ng-model="test"></div>
<div>From parameter: <input type="text" ng-model="testParam"></div>
</div>
<div class="modal-footer"></div>
</div>
</div>
</div>
</div>
As you can see, the controller's scope in Method 1 is not the nearst scope that the selectedFiles is defined, that why the $scope.selectedFiles and $scope.test are undefined.
You could workaround the issue by keeping the selectedFiles in some object before put it in scope, e.g. $scope.model.selectedFiles. Please see the plunker below for an example.
Method 1 Plunker (Modified): http://plnkr.co/edit/pP2L1ZJLxXJXgqR3QAIT?p=preview
Hope this clear things up!

Directives inside an ng-show

I'm seeing some odd behaviour when I put custom directives inside a div with an ng-show.
If I define the html as:
<div ng-show="service.searchResults">
<fig-search-type-filters />
<fig-filter-search />
<div class="gridStyle" ng-grid="vm.grid"></div>
</div>
then when the show condition is true it shows just fig-search-type-filters content. All the rest is elided from the html.
However, if I wrap each directive as follows:
<div ng-show="service.searchResults">
<fig-search-type-filters />
</div>
<div ng-show="service.searchResults">
<fig-filter-search />
</div>
<div ng-show="service.searchResults">
<div class="gridStyle" ng-grid="vm.grid"></div>
</div>
then fig-search-type-filters, fig-filter-search and the grid are displayed as I expect. Why is this?
If I move the ng-show condition inside the template for each directives then again only the fig-search-type-filters appears.
What if you do this:
<div ng-show="service.searchResults">
<fig-search-type-filters> </fig-search-type-filters>
<fig-filter-search> </fig-filter-search>
<div class="gridStyle" ng-grid="vm.grid"></div>
</div

Resources