How to share directive scope with transcluded elements? - angularjs

I am trying to create a directive with transclusion. Issue is the scope of transcluded part is different from that of the directive scope.
I have tried it on plnkr here is the link - http://plnkr.co/edit/vDfXs9zyfAngPqmFCiZR?p=preview
I want the directive to have isolated scope yet be able to switch among the tabs, based on which button is clicked.
index.html
<maple-multistep-form steps="steps">
<span>transcluded scope - {{selection}}</span>
<div ng-switch="" on="selection">
<!-- First Step -->
<div ng-switch-when="Step 1: Team Info">
<ng-include src="'step1.html'"></ng-include>
</div>
<!-- Second Step -->
<div ng-switch-when="Step 2: Campaign Info">
<ng-include src="'step2.html'"></ng-include>
</div>
<!-- Third Step -->
<div ng-switch-when="Step 3: Campaign Media">
<ng-include src="'step3.html'"></ng-include>
</div>
</div>

Try passing the 'selection' to the directive.
http://plnkr.co/edit/23QO2UGvKwwXK7PRo2bJ?p=preview
<maple-multistep-form steps="steps" selection="selection">
and inside
scope: { //comment scope to make the directive have shared scope, this makes the directive work fine
steps: '=',
selection: '=?'
},

Related

Asynchronously issue

Use case:
In my index.html. I have two ng-include directives and two custom directives.
Each directive has its src attribute defined the address to load some html snippet from another server (the custom directives have its template attribute loading the html)
I need to make sure that all directives (see the code 1,2,3) have fully loaded the html snippets before I call the fifth ng-include directive (4) to load the java-script from another server.
Right now all directives are running asynchronously and I can't be sure that the directive which loads the Javascript should always be the last one to load. I need this because if Javascript is loaded and executed before the html it wouldn't go through the html and linking it to javascript/jQuery.
I have solved this issue using a very classic way. I update a global variable whenever the html snipts are loaded and define the an ng-include directive to load the Javascript conditionally when the global variable is true indicating all other directives have loaded their html. But this is not the Angular way. I need the Angular way and btw I'm new starter on Angular.
Index.html
<div ng-controller="navigationController" >
**(1)**<agilesites-navbar></agilesites-navbar>
<div class="container-fluid">
<div class="row row-offcanvas row-offcanvas-right">
<div id="offcanvas-curtain" class="hidden-lg"></div>
<div id="contentContainer" class="col-sm-12 col-md-12 col-lg-12">
<div ui-view="content" id="content"></div>
</div>
**(2)**<agilesites-sidebar></agilesites-sidebar>
</div>
</div>
**(3)**<ng-include src="''+API_HOST+'api/cms/footer'" onload="cmsResourceLoadingStatus(true)"></ng-include>
**(4)**<ng-include src="''+API_HOST+'api/cms/javascript'" ng-if="cmsResourceLoadingCompleted"></ng-include>
</div>
Custom directive (agilesites-sidebar):
angular.module('newhorizonsApp')
.directive('agilesitesSidebar', ['ProductTypes','$compile', function (ProductTypes,$compile) {
return {
restrict: 'E',
scope: true,
template: '<ng-include src="\'\'+API_HOST+\'api/cms/sidebar\'" onload="executeOnLoad();"></ng-include>',
link: function (scope,elem){
scope.executeOnLoad = function() {
scope.cmsResourceLoadingStatus(true);
};
}
};
}]);

how to have unique scope in ng-repeat in angularjs

Is it possible for me to have multiple array for $scope?
i have a list of div with child scopes that is generated from a parent scope in ng-repeat. How can i have the scope variable individually unique?
I am generating a list of ng-repeat in another ng-repeat.
<div ng-repeat="" ng-init="hide=true" ng-click="hide=!hide">
<div ng-hide="hide" ng-init="childhide=true" ng-click="childhide=!childhide">
<div ng-repeat="" ng-init="childhide" ng-hide="childhide">
<div>{{ variable }}</div>
</div>
</div>
</div>
How can i have the variable unique? Coz each time when i click on either one div, all div with childhide variable will show. Anyway to make them behave individually?
Thanks.
To get a new $scope for each div, the first ting that comes to mind is to create another directive and specify which type of scope you want.
<div class="container">
<div ng-repeat="item in items"></div>
</div>
will become:
<div class="container">
<inner-directive ng-repeat="item in items"></inner-directive>
</div>
then in inner-directive:
app.directive('innerDirective', function () {
return {
restrict: 'E',
template: '<div></div>', // this replaces what you had before
scope: {}
};
});
This will create an isolate scope, which does not inherit properties from it's parent.
There are a couple of other scope options but i cant remember off the top of my head what each one does. Easy to read in the docs though.

using ng elements outside of ng-controller

I am inexperienced with angular.
I am using angular to create a series of nested divs (a form) on a webpage. The top div has ng-controller="controllername" as an attribute. Within the nested divs is a div with ng-show="showvar" as an attribute.
It looks like this.
<div class="page">
<div ng-controller="controllername">
<div ng-show="showvar">Hidden Stuff</div>
</div>
</div>
When I perform functions on showvar to make it true, the div appears (and disappears when false) as intended.
I also have a completely separate div 'outside' the the original nest of divs with the ng-controller attribute. As such, there is no ng-controller attribute in this seperate hierarchy BUT I have nested another div inside with the ng-show="showvar" attribute.
Updated HTML structure is as such
<div class="page">
<div ng-controller="controllername">
<div ng-show="showvar">Hidden Stuff</div>
</div>
<div class="seperate">
<div ng-show="showvar">More Hidden Stuff</div>
</div>
</div>
When the page loads, both divs with ng-show="showvar" in the separate nests are hidden as ng-hide has been appended by angular. When I perform functions on showvar after the page load to make it true, only the div within the ng-controller div gets shown.
I (think I) understand this is because the ng elements are evaluated at page load (and appended with ng-hide, even outside the controller?) but only the ng elements within the div with the ng-controller attribute are evaluated when functions are performed after page load. Is this correct?
How can I get the other ng-show to be evaluate 'outside' of the ng-controller div?
I was thinking one option is to append ng-controller to the overall 'page' div instead of the nested div. But what other options do I have?
EDIT: I also tried simply adding ng-controller="controllername" to the separate div. I guess angular 'ignores' the duplicate ng-controller div?
The problem your facing is that the showvar resides in your controller's scope, your second usage of the showvar is not within that scope.
What you need to do is make sure the variable is available where needed.
Say you add the variable to the parentController (you don't have one in your example so I'll add one)
<div class="page" ng-controller="parentController">
<div ng-controller="controllername">
<div ng-show="showvar">Hidden Stuff</div>
</div>
<div class="seperate">
<div ng-show="showvar">More Hidden Stuff</div>
</div>
</div>
app.controller('ParentController', function($scope){
$scope.showvar = false;
});
problem with this is when you set showvar to true within your controllername controller it will set it in the innerscope and not the outer. When making sure you have the right scope by accessing it through another object you should be safe.
So try it like this:
<div class="page" ng-controller="parentController">
<div ng-controller="controllername">
<div ng-show="obj.showvar">Hidden Stuff</div>
</div>
<div class="seperate">
<div ng-show="obj.showvar">More Hidden Stuff</div>
</div>
</div>
app.controller('ParentController', function($scope){
$scope.obj = {
showvar: false
}
});
Quick demo
Your issue here is that you ended with 2 "showvar" variables: one within the "controllername" scope and another one on the app scope (as you have a ng-app declaration somewhere in your html parent of the "page" div).
When you load your page, you get the value of "showvar" in the controller scope for the first div, and for the "separate" one, you get the "showvar" variable in the app scope, which doesn't exist, therefore it is resolved to "false" (even though angular declares it for you in your app scope and you can even modify its value later).
When you change the value of "showvar" in the controller scope, it doesn't change the one in the app scope, making the "separate" div stay hidden forever =)

$scope.$emit not working while $rootScope.$broadcast does

Here's my parent controller where I listen for the event
app.controller("SectionLayoutController", function($scope) {
$scope.$on("sectionLayout.doAction", function(e, options) {
// do some stuff
});
});
And in my child controller I do this
$scope.$emit("sectionLayout.doAction", { someOption: "something" });
The event should be triggered in the parent controller but it isn't. I did check what the contents of my child controller's scope was (with console.log) right before emitting the event, and the listener for the event does exist in one of its parents.
Maybe it's because the child controller's scope is not a direct child of the scope where the event is being listened on? This is not really a critical issue because I can just replace the line with
$rootScope.$broadcast("sectionLayout.doAction", { someOption: "something" });
and it works fine, but I'm still curious as to why $scope.$emit isn't working (it did work at some point, it just stopped randomly).
(Angular version 1.2.16)
EDIT
The problem seems to be a bug in angular.js, not ui-router (it has already been reported and is scheduled for a fix in version 1.3 https://github.com/angular/angular.js/issues/5489). Using ng-transclude inside a directive seems to create a sibling scope for the directive, which makes this kind of hierarchy (with my first plunker example) : http://i.imgur.com/4OUxJSP.png.
So yeah, I guess for now i'll be using $rootScope.$broadcast
Ok, so from what I've found, this is a bug with ui-router (forgot to mention I was using that, oops).
If the ui-view of a child route is inside a transcluded directive, events will get stuck there. Here's a plunkr of that bug in action http://plnkr.co/edit/zgqAPiDbB2LUtwJaeHhN?p=preview. The Dummy controller uses the sectionLayout directive and the ui-view is transcluded (as you can see in dummy.html).
<!-- dummy.html -->
<div section-layout="layout">
<!-- transcluded stuff -->
<div ui-view></div>
</div>
<!-- sectionlayout.html -->
<div>
<p>Section Layout for {{layout.title}}</p>
<p ng-repeat="r in recieves">{{r.message}}</p>
<div ng-transclude style="background-color: #EEE;"></div>
</div>
Here's another plunkr where $scope.$emit does work, and the only difference is that the ui-view is directly inside sectionlayout.html instead of transcluded in the directive http://plnkr.co/edit/mVftwkZrynkF6KanE4zV?p=preview.
<!-- dummy.html -->
<div section-layout="layout"></div>
<!-- sectionlayout.html -->
<div>
<p>Section Layout for {{layout.title}}</p>
<p ng-repeat="r in recieves">{{r.message}}</p>
<div ui-view style="background-color: #EEE;"></div>
</div>
And here's a completely different plunkr where I don't use ui-router, but it's still the same thing. A directive is used in a parent where events are listened, and the child controller is transcluded in the directive. In this one, both $scope.$emit and $rootScope.$broadcast work fine, so it seems to be a bug with transcluded ui-views in ui-router. http://plnkr.co/edit/Iz5YcbMiTzrXQ6bJMskK?p=preview
<div ng-controller="ParentController">
<p>Parent Controller</p>
<p ng-repeat="r in recieves">{{r.message}}</p>
<div my-directive>
<div ng-controller="ChildController" style="background-color: #EEE;">
<p>Child Controller</p>
<button ng-click="tryEmit()">$scope.$emit</button>
<button ng-click="tryBroadcast()">$rootScope.$broadcast</button>
</div>
</div>
</div>
I'll report this bug on their github page

how to make tags within the custom directives go to specific place of template?

I have a directive by name cngView
controller.js
//directive to include view panel
angular.module('cngFoundation', [])
.directive('cngView', function() {
return {
restrict : 'CAME',
templateUrl: 'templates/view.html'
};
});
to access directive
<div ng-app="cngFoundation">
<div id="mainbody" class="container">
<cng-view>
[AAA]
</cng-view>
</div>
</div>
The templates/view.html is as follows
<!-- View Panel-->
<div class="panel panel-primary " id="view" >
<div class="panel-heading">
<h3 class="panel-title" ><b>View</b></h3>
</div>
<div class="panel-body">
[BBB]
</div>
</div>
I have made the cng-view tag work but I don't know how to give contents inside the tag.
How to make contents I give at place "[AAA]" appear at "[BBB]" .
In short how to make contents I give within cng-view tag(a custom tag) go into the specific place inside the template content of the cng-view .
Please give guidance . At least please direct me to some online tutorial .
Thanks
You need to use ng-transclude which tells template where to place existing content
<div class="panel-body" ng-transclude></div>
Also need to set transclude:true in directive
See section Creating a Directive that Wraps Other Elements in directive docs
You can use the built in ng-transclude directive. In your view.html replace [BBB] with
<span ng-transclude></span>
also be sure to define transclude on your directive
transclude: true,
For a working example see this plunkr

Resources