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

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

Related

ng-keyup firing but not reading properly when used with an ng-if inside a directive template

I have a directive and it works fine in a way such that when I type something the search() scope function inside my directive fires and sets $scope.query with the input text.
here is the directive template
<div class="container">
<div class="system-filter-header">
<div class="no-gutter">
<div class="system-search-wrapper search-wrapper-width">
<i ng-click="search($evt)" class="fa fa-search"></i>
<input type="text" ng-keyup=search($evt) class="search pull-left suggesstions-styles"
ng-model="query" ng-attr-placeholder="Search...">
</div>
</div>
</div>
</div>
here is the scope function which gets triggered
$scope.search = function() {
console.log($scope.query.length)
}
But when I used an ng-if="true" in first line of template (true used for generalizing only, I want to do a different conditional check inside ng-if) such that,
<div class="container" ng-if="true">
still the search gets triggered but the console.log gives always 0 and it doesn't seem to update the $scope.query value as it stays as $scope.query = ''
throughout the typing.
EDIT
Here is a an example codepen with almost similar behaviour. The problem is with the searchBox directive and I have added ng-if=true to the template but searching doesn't work. When I remove the ng-if searching works fine.
Any reason for this?
Rule of thumb in AngularJS: your ng-model should always include a dot. Otherwise AngularJS directives that create child scopes (like ng-if or ng-repeat) will create a duplicate property on that child scope instead of the parent scope. Following the controllerAs convention completely mitigates this behavior.

Adding partial views and controller in angular app

I am in the process of optimizing the performance of Angular app. I am using ng-include to add partial views and its controllers. Following is the code snippet
<!--show term rule view-->
<div id="showTermRule" ng-controller="TermRuleController as term" ng-if="showTermRule">
<div data-ng-include src="'/Relevancy/termRule/termRule.html'" ng-if="!isPublsihed"></div>
<div data-ng-include src="'/Relevancy/termRule/publishedTermRule.html'" ng-if="isPublsihed"></div>
</div>
<!--show function rule view-->
<div id="showFunctionRule" ng-controller="expressionBuilderController" ng-if="showFunctionRule">
<div data-ng-include src="'/Relevancy/functionRule/functionRule.html'" ng-if="!isPublsihed"></div>
<div data-ng-include src="'/Relevancy/functionRule/publishedFunctionRule.html'" ng-if="isPublsihed"></div>
</div>
<div id="showQueryRule" ng-controller="queryBuilderController" ng-if="showQueryRule">
<div data-ng-include src="'/Relevancy/queryRule/queryRule.html'" ng-if="!isPublsihed"></div>
<div data-ng-include src="'/Relevancy/queryRule/publishedQueryRule.html'" ng-if="isPublsihed"></div>
</div>
I have a parent controller from where I am making "showTermRule" variable true and broadcasting an event as follows
switch (rule) {
case "Term Rules":
$scope.currentRuleDisplayed = 'Significant Terms';
$scope.showTermRule = true;
$rootScope.$broadcast('updateTermRule',$scope.profileTree[$scope.currentProfile].termRules,$scope.currentProfile,$scope.profileTree[$scope.currentProfile].id);
break;
The problem I am facing is when I use ng-if in child controller, say TermRuleController, it is not able to catch the broadcasted event from parent controller. As per my understanding it is because by the time I am broadcasting event div element which is adding controller is not getting added to DOM.
I have tried same thing using ng-show. It is working then but then it is taking very long to load the page. Can someone suggest the right way to add partial views and controller. After some research I have found that instead of using ng-include I can use directive. I am yet not sure about it.
Also I guess writing service instead of broadcasting might solve the problem but my question is, is it the correct way to add partial views having different controllers?
You need to keep in mind when using ngInclude to split your templates into partials. Instead of applying a controller to an element in a layout-template, apply the controller to an element in the partial. That way you’ll not need to target its parent’s scopes, coupling controllers together by scope. Here’s an example: Layout.html
<div ng-controller="LoginCtrl">
<div ng-include="login.html"></div>
</div>
Login.html :
<form-field ng-field-data="{{login.usr_name}}"></form-field>
<form-field ng-field-data="{{login.password}}"></form-field>
In the case above, you would want to handle the login model in the
LoginCtrl controller, but the scope of the login partial login.html
will be one step deeper. Instead, define the controller on the same
level as the partial (see below).
Layout.html: <div ng-include="login.html"></div>
Login.html:
<div ng-controller="LoginCtrl">
<form-field ng-field-data="{{login.usr_name}}"></form-field>
<form-field ng-field-data="{{login.password}}"></form-field>
</div>
So, in this way $scope of parent and child controller would be same.
So, in your case you don't have to broadcast an event , it would be directly available in the child controller.
Hope, this articles helps you out from the problem that you are dealing.

AngularJS - One controller on two sibling DOM elements

I'm trying to do a very simple thing. I'm displaying a list of values with Edit links beside them. Clicking the edit link reveals a form that lets you update the value.
(I've simplified the question so the items just have one field "name". My actual use case has more fields, but is canonically equivalent.)
I've run into something that looks like a bug in Angular, but given my limited experience with it, I'm not so sure. Here's how I'm trying to do it:
<div ng-repeat-start="item in items" ng-controller="ItemCtrl as ctrl" ng-hide="ctrl.isEditing">
<span>Name: {{item.name}}.</span>
<a href='#' ng-click='ctrl.startEditing()'>Edit</a>
</div>
<div ng-repeat-end ng-show="ctrl.isEditing">
<input type='text' ng-model='item.name'/>
<a href='#' ng-click='ctrl.save()'>Save</a>
</div>
My controller looks like this:
app.controller('ItemController', function() {
this.isEditing = false;
this.startEditing = function() { this.isEditing = true; }
this.save = function() { this.isEditing = false; }
});
Clicking on Edit link calls the right controller function, and the first div hides. But the second div does not hide.
When I rearrange the code to look like this (essentially wrapping the two divs with a wrapper element), all is well.
<div ng-repeat="item in items" ng-controller="ItemCtrl as ctrl">
<div ng-hide="ctrl.isEditing">
<span>Name: {{item.name}}.</span>
<a href='#' ng-click='ctrl.startEditing()'>Edit</a>
</div>
<div ng-show="ctrl.isEditing">
<input type='text' ng-model='item.name'/>
<a href='#' ng-click='ctrl.save()'>Save</a>
</div>
</div>
Any idea what is technically wrong with the first version? Note that the <input> boxes do get populated with the right values from item.name.
PS: There's a reason why I'm trying to keep the two divs siblings: in my use case, they are actually implemented as two trs which are supposed to appear right below each other in a table.
It's not a bug from angular but it is quite logical.
<div ng-repeat-start="item in items" ng-controller="ItemCtrl as ctrl" ng-hide="ctrl.isEditing">
<span>Name: {{item.name}}.</span>
<a href='#' ng-click='ctrl.startEditing()'>Edit</a>
</div>
<div ng-repeat-end ng-show="ctrl.isEditing">
<input type='text' ng-model='item.name'/>
<a href='#' ng-click='ctrl.save()'>Save</a>
</div>
If you see the above code you have injected controller only to the first div so obviously sibling div doesn't know what is ctrl or ItemCtrl until and unless you do as in you second way.
So if you want to achieve it as sibling, if you are using routing then add the controller attribute in your route path.
So that the controller will be active for that entire template and you can achieve what you want.
Hope it helps.
Thats because controller has its own scope. When you placed controller ONLY on your first div controllers' scope is limited to only this one div. When you wrapped both your divs inside another and place controller on wrapper, controllers' scope now is all that inside this wrapper. So in this case this works fine and this is not the angular bug

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 =)

get ng-click, on injected element, to change the class of an element already in the view?

I have a <ul> that gets populated with the server. But in that controller there is also an iframe. When the <li>'s arrive there is some disconnect between them and the iframe even though they are in the same controller.
When you click one of the li's it should change the class on the iframe but it's not. However, If I move the iframe inside of the ng-repeat that injects the iframe it works.
View
<div class="content" ng-controller="FeedListCtrl">
<ul>
<li ng-repeat="item in items">
<div data-link="{{item.link}}" ng-click="articleShowHide='fade-in'">
<div ng-bind-html="item.title" style="font-weight:bold;"></div>
<div ng-bind-html="item.description"></div>
<!-- it works if i put the iframe here -->
</div>
</li>
</ul>
<!-- doesn't work when the iframe is here -->
<iframe id="article" ng-class="articleShowHide" src=""></iframe>
</div>
Here is the controller. It does an ajax call to get the data for each <li>
Controller
readerApp.controller('FeedListCtrl', ["$scope", "$http", "FeedListUpdate", function ($scope, $http, FeedListUpdate) {
$scope.setFeed = function (url) {
$http.get('feed?id=' + FeedListUpdate.GetCurrentFeedUrl()).success(function (data) {
$scope.items = data.currentFeed.items;
});
};
}]);
When inside of an ng-repeat you are in a different scope which means you are not setting the variable you think you are. Use $parent and that should work. The syntax is:
<div data-link="{{item.link}}" ng-click="$parent.articleShowHide='fade-in'">
Side note for others finding this - sometimes adding curly brackets helps as well. For more information on ng-class see here: http://docs.angularjs.org/api/ng/directive/ngClass
An Example
In case anyone wants to see this in action, I put together an example demonstrating a few ways to set the class as well as demonstrating the issue in scope (See: http://plnkr.co/edit/8gyZGzESWyi2aCL4mC9A?p=preview). It isn't very pretty but it should be pretty clear what is going on. By the way, the reason that methods work in this example is that the scope doesn't automatically redefine them the way it does variables so it is calling the method in the root scope rather than setting a variable in the repeater scope.
Best of luck!

Resources