AngularJS controller communication with nested isolate scope directives - angularjs

In my AngularJS application, I have to create an accordion with each section containing a slider control. I want both the accordion and the slider to expose APIs to the outer controller, for example:
accordion.collapseAll()
accordion.openNextPane()
or
slider.resetValue()
And I would like to be able to do in the controller something similar to:
$scope.clickNext = function() {
$scope.accordion.openNextPane();
};
$scope.clickResetAll = function() {
$scope.items.forEach(function(item, ind) {
$scope.slider[ind].resetValue();
// or:
$scope.accordion.pane[ind].slider.resetValue();
});
}
I'm thinking of creating isolate scope directives for the accordion and the slider, with a two-way binding to allow them to expose their methods to a parent controller. The page template would then look like this:
<div ng-controller="myController">
<accordion data="accordion">
<div ng-repeat="c in collection">
<accordion-pane>
<slider data="slider"></slider>
</accordion-pane>
</div>
</accordion>
</div>
But at this point I'm lost in a maze of isolate scopes and limited visibility on children, and it seems that there is no way to access the inner elements from the outer controller. Many solutions I've googled around seem to consider normal to have the directive talking to the controller instead of the other way round, but this seems to break encapsulation.
Any idea on how to do this? What is the correct AngularJS pattern for this problem?

Related

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: create element dynamically

How do I go about create an element in my controller? e.g. on a click event?
example controller:
function AddCtrl($scope){
$scope.add = function(){
// do stuff to create a new element?
}
}
example view:
<div ng-controller="AddCtrl">
<button ng-click="add()">Add</button>
// create <input type="text" ng-model="form.anotherField">
</div>
Any suggestions much appreciated.
AngularJS is intended to follow MVC - so the controller creating an element in the view doesn't agree with the MVC behavior. The controller should not know about the view.
It sounds as if you want to have a control appear based on some conditional logic. One approach would be to bind to the visibility of the element.
In Angular, your controllers should not be manipulating the DOM directly. Instead, you should describe the elements you need in your templates, and then control their display with directives, like ng-switch, ng-hide / ng-show, or ng-if, based on your model, ie, your data.
For example in your controller you might do something like:
$scope.showForm = false;
And then in your partial:
<div id="myForm" ng-show="showForm">
<!-- Form goes here -->
</div>
By switching $scope.showForm between true and false, you will see your myForm div appear and disappear.
This is a classical mistake coming from jQuery moving to Angular or any other MVC library. The way you should think is to let the view react to changes in the scope.
$scope.items = []
$scope.add = function(){
$scope.items.push({});
}
In the view:
<input type="text" ng-repeat="item in items" ng-model="item.property">
If you want to display an element based on some condition or after the click, use ng-switch: http://docs.angularjs.org/api/ng/directive/ngSwitch
If you want to add multiple elements, create a repeated list of items and add an item to your view-model on clicking the button:
$scope.yourlistofitems = [];
$scope.add = function() {
$scope.yourlistofitems.push("newitemid");
}
And in the HTML:
<input type="text" ng-repeat="item in yourlistofitems" ng-model="item.property">

how to use angular's ng-repeat filter with input outside the controller?

I want to use the angular's ng-repeat filter like so:
<div ng-repeat="trade in trades | filter:searchTrades | orderBy:predicate:reverse">
the problem here is the input control where I want to bind "searchTrades" to exists OUTSIDE the controller and view where the ng-repeate exists. the input field exists outside the controller for a good reason. it's a global search input that i intend to use differently with each controller. so further more I will need to give the search input different behavior depending on which controller/view is active.
This is a question of scopes, and eventing between scopes. As angular uses prototype inheritance, you can still gain access to "parent" scope properties and react to them.
The short of it, if you have searchTrades on a parent controller, the child controller can access it. Note if the child controller modifies searchTrades it will make a "new copy", if you need to do that use $scope.$emit and $scope.$on
Here is a plunker to look at
Consider the following
Controllers
function MainCtrl($scope, ...) {
$scope.search = 'My search term'
}
function ChildCtrl1($scope, ...) {
$scope.items = ['Foo', ... ]
}
View
<body ng-controller="MainCtrl">
<label>Search</label> <input ng-model="search" />
<div ng-controller="ChildCtrl1">
<ul>
<li ng-repeat="item in items | filter:search">{{item}}</li>
</ul>
</div>
</body>
ChildCtrl1 will inherit search from the parent controller, and it can be used as "normal"

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!

Angular access controller scope from nested directive

this fiddle represents what i am trying to do:
http://jsfiddle.net/d1001001/dwqw6/.
The grid directive needs to grab some data from controller, but since it's nested in the modal directive, which has isolated scope, it doesn't have access to controller's scope. If i
put it like this
<div ng-controller="MyCtrl">
<grid data="data" cols="cols"></grid>
</div>
it works.
Is there a solution to this? I don't feel like passing the data and cols variables to the modal directive as well. Thanks
Use
<div ng-transclude></div>
instead of
<ng-transclude></ng-transclude>
in the template of modal directive.

Resources