Using same controller in two places in AngularJS - javascript

I am working on an Ionic app, screen has lists with checkboxes and option to select all. I am new to AngularJS and Ionic.
"Select All" works fine when using controller in parent element where Select all and other lists reside.
I want to move the Select All to sub-header so that "Select All" will be always visible when we scroll through.
I tried to use the same controller in both places but Select All didn't work, I just read the scope gets changed and the value won't get passed.
Is there any way to pass the changes or any other way to fix this?
And data will be populated from the services.
HTML
<ion-header-bar class="bar-light bar-subheader">
<div ng-controller="MainCtrl">
<ion-checkbox ng-model="selectAll" ng-click="checkAll()" >
<p>Select All</p>
</ion-checkbox>
</div>
</ion-header-bar>
<ion-content class="has-header">
<div class="list" ng-controller="MainCtrl">
<ion-checkbox ng-repeat="item in devList" ng-model="item.checked">
<div class="row">
<div class="col">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
<div class="col right">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
</div>
</ion-checkbox>
</div>
</ion-content>
JS
.controller('MainCtrl', function($scope) {
$scope.devList = [
{ text: "HTML5", checked: true },
{ text: "CSS3", checked: false },
{ text: "JavaScript", checked: false }
];
$scope.checkAll = function() {
if ($scope.selectAll) {
$scope.selectAll = true;
} else {
$scope.selectAll = false;
}
angular.forEach($scope.devList, function (item) {
item.checked = $scope.selectAll;
});
};
});
CodePen link

Each controller will have it's own $scope. So, two different instances of controllers might have the same code, but they will still have different scopes.
So, you want to pass the changes from one controller to another.
In that case, there are a few solutions:
Using events:
$scope has a few methods which can help you to handle these cases.
These methods:
$on(name, listener) - Listens on events of a given type/name.
$emit(name, args) - Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
$broadcast(name, args) - Dispatches an event name downwards to all child scopes (and their children) notifying the registered $rootScope.Scope listeners.
These methods will allow you to rise events from one controller and handle them inside the others.
Shared services
Also, you can create the service which will be injected into different controllers and, lets say, first controller will read this shared data and the second will write the data to this shared service.
Here is an article with some examples - link.
You can chose the approach that you like more, but I prefer shared services. This approach keeps my controllers more clear and I can manage cross-controllers dependencies by injection this shared services.
Hope it will help you.

You cannot use 2 'ng-controllers' with same controllers because the scopes created from those 2 controllers will be different, as controller scopes are created using the constructor pattern.
Ideally you should use $stateProvider to define your routes and their corresonding template and controller like below:
But for simplicity sake I have forked your codepen and used a single controller at the parent level of the view and it is working fine: http://codepen.io/anon/pen/vGQJNj
<body ng-controller="MainCtrl">
<ion-header-bar class="bar-positive">
<h1 class="title">Checkboxes</h1>
</ion-header-bar>
<ion-header-bar class="bar-light bar-subheader">
<div>
<ion-checkbox ng-model="selectAll" ng-click="checkAll()" >
<p>Select All</p>
</ion-checkbox>
</div>
</ion-header-bar>
<ion-content class="has-header">
<div class="list">
<ion-checkbox ng-repeat="item in devList"
ng-model="item.checked"
ng-checked="item.checked">
<div class="row">
<div class="col">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
<div class="col right">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
</div>
</ion-checkbox>
<div class="item">
<pre ng-bind="devList | json"></pre>
</div>
<div class="item item-divider">
Notifications
</div>
<ion-checkbox ng-model="pushNotification.checked"
ng-change="pushNotificationChange()">
Push Notifications
</ion-checkbox>
<div class="item">
<pre ng-bind="pushNotification | json"></pre>
</div>
<ion-checkbox ng-model="emailNotification"
ng-true-value="'Subscribed'"
ng-false-value="'Unubscribed'">
Newsletter
</ion-checkbox>
<div class="item">
<pre ng-bind="emailNotification | json"></pre>
</div>
</div>
</ion-content>
</body>

Related

Angular http data displaying in one div but not the other

I am using ng-repeat to print a list of posts to the page via the WordPress REST-API. I am using Advanced Custom Fields on each post. From what I can see everything is working, but the post data is not showing in one container, yet it is displaying in another. I should also mention that this is set up like tabs. (user clicks a tab for a post and it displays that posts data)
var homeApp = angular.module('homeCharacters', ['ngSanitize']);
homeApp.controller('characters', function($scope, $http) {
$scope.myData = {
tab: 0
}; //set default tab
$http.get("http://bigbluecomics.dev/wp-json/posts?type=character").then(function(response) {
$scope.myData.data = response.data;
});
});
homeApp.filter('toTrusted', ['$sce',
function($sce) {
return function(text) {
return $sce.trustAsHtml(text);
};
}
]);
HTML:
<section class="characters" ng-app="homeCharacters" ng-controller="characters as myData">
<div class="char_copy">
<h3>Meet the Characters</h3>
<div ng-repeat="item in myData.data" ng-bind-html="item.content | toTrusted" ng-show="myData.tab === item.menu_order">
<!--this is the section that will not display-->
<h3>{{ item.acf.team }}</h3>
<h2>{{ item.acf.characters_name }} <span>[{{item.acf.real_name}}]</span></h2>
<p class="hero_type">{{ item.acf.hero_type }}</p>
{{ item.acf.description }}
Learn More
</div>
</div>
<div class="char_tabs">
<!--if I put the identical {{}} in this section it WILL display-->
<nav>
<ul ng-init="myData.tab = 0" ng-model='clicked'>
<li class="tab" ng-repeat="item in myData.data" ng-class="{'active' : item.menu_order == myData.tab}">
<a href ng-click="myData.tab = item.menu_order">
<img src="{{ item.featured_image.source }}" />
<h3>{{ item.title }}</h3>
</a>
</li>
</ul>
</nav>
</div>
</section>
I should also mention that I use Ng-inspector, and it does show the data being pulled in. I can confirm this via the console. I have checked to ensure no css is in play; the div is totally empty in the DOM.
I appreciate all the help the GREAT angular community has shown!
The problem is you had used ng-bind-html over ng-repeat element which is changing the inner content of ng-repeat div. I guess as you have inner transcluded template inside ng-repeat directive, you should not be using ng-bind-html there in a place.
Markup
<div ng-repeat="item in myData.data" ng-show="myData.tab === item.menu_order">
<!--this is the section that will not display-->
<h3>{{ item.acf.team }}</h3>
<h2>{{ item.acf.characters_name }} <span>[{{item.acf.real_name}}]</span></h2>
<p class="hero_type">{{ item.acf.hero_type }}</p>
{{ item.acf.description }}
Learn More
</div>

Show a region on page if data from json equal to true with Ionic and Angular

I'm creating an ionic app and there are variables I request from a php page through the Ionic controller. From the variables I get, I have a field called active which either be yes or no for each user after log on.
What I want to achieve is, there is a div on the page called "activediv" and I want that div show only if the variable from the json = yes.
.controller('useraccount_ctrl', ['$scope', '$http', function($scope, $http) {
$http.get('http://localhost/myapp/app_ion/templates/user/user_account.php').success(function(data) {
$scope.userdata = (data);
});
}])
HTML
<ion-content class="has-subheader">
<ion-list class="list card" ng-repeat="item in userdata">
<ion-item href="#/tab/source/{{item.profile_id}}">
<div class="item item-avatar-big">
<img src="../usr_up_img/{{item.profile_pix}}">
<h2>{{item.fname}}</h2>
<p>{{item.country}}</p>
<p>{{item.curr_city}}</p>
<div id="activediv">{{item.active}}</div>
</div>
</ion-item>
</ion-list>
</ion-content>
JSON
[{"fname":"Nicholas","country":"India","curr_city":"East Legon","profile_id":"1298","profile_pix":"173333297082.jpg","active":"yes"}]
Try this
<div id="activediv" ng-if ="item.active == 'yes'">{{item.active}}</div>
Pravesh's answer is good.
You can also use ng-show and it will just hide or show your div's content:
<div id="activediv" ng-show="item.active == 'yes'">{{item.active}}</div>
use ng-if
<ion-content class="has-subheader">
<ion-list class="list card" ng-repeat="item in userdata">
<ion-item href="#/tab/source/{{item.profile_id}}">
<div class="item item-avatar-big">
<img ng-if ="item.profile_pix && item.active =='yes'" src="../usr_up_img/{{item.profile_pix}}">
<h2 ng-if ="item.fname && item.active =='yes'">{{item.fname}}</h2>
<p ng-if ="item.country && item.active =='yes'">{{item.country}}</p>
<p ng-if ="item.curr_city && item.active =='yes'">{{item.curr_city}}</p>
<div id="activediv" ng-if ="item.active =='yes'">{{item.active}}</div>
</div>
</ion-item>
</ion-list>

Directive take other directive's data after deletion

Edit: Thanks to Simon Schüpbach, I was able to resolve the issue by changing the template. See the end for the solution.
Let's preface this by saying that we are beginner to soft-intermediate in Angular.
On one of our project, we are using angularjs 1.4.x and also ng-cart (https://github.com/snapjay/ngCart). It worked great but then we were confronted with a demand from our client that created new weird issues.
We added fsCounter, as a directive, to the cart page so user can add or remove items. This all work great but the users also have the option to delete an item from the cart view. Deletion works as expected BUT it seems to affect the scope to the item that takes it place.
Let me make it clearer :
Let's say we have 2 products in our cart page, it displays something like that
Product_1 {price} {counter} {total} delete_btn
Product_2 {price} {counter} {total} delete_btn
Each fsCounter is its own scope
return {
restrict: "A",
scope: {
value: "=value",
index: "=index"
},
link: //other logic
However when we delete the first item, visually and in the directives, the data seems to shift. So our second row will now inherit the first row's counter.
Directive's data looks like this:
Product_1:
itemId:3,
quantity:2,
{other data}
Product_2:
itemId:8,
quantity:5,
{other data}
But once we delete the first directive (We get the scope, remove the DOM element, destroy the scope) the second directive will now have this data:
Product_2:
itemId:3,
quantity:2,
{other data}
Here is the template code :
<div class="unItem" ng-repeat="item in ngCart.getCart().items track by $index">
<div class="photo"><img src="{[{ item.getImage() }]}" alt=""></div>
<div class="details">
<h3>{[{ item.getName() }]} <span>{[{ item.getPrice() | currency:$}]}</span></h3>
<md-select ng-model="attributes" placeholder="Attribut" class="select-attribut" ng-show="item.hasAttributes()" ng-change="item.updateSelected(attributes)">
<md-option ng-repeat="attr in item.getAttributes()" ng-selected="attr == item.getSelected()" ng-value="attr">{[{ attr }]}</md-option>
</md-select>
</div>
<div class="quantity">
<div fs-counter-dynamic value="itemQuantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="itemQuantity"
name="quantity" id="quantity-{[{ item.getId() }]}",
index="{[{ item.getId() }]}"
></div>
</div>
<div class="total">Total : {[{ item.getTotal() | currency }]}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.getId());"></a></div>
</div>
Is this normal behavior? Is there any way to force the directive to keeps its own data? From what I've understood, each directive has its own scope, so what I think happens is that, when we remove the first one, it keeps the data stored in some kind of array that says "directive 1 data is : " and when we delete the first directive, the second one becomes the first.
So basically, are we doing anything wrong or is there anyway to remap the data?
Hope it was clear enough,
Thanks!
Edit: added html code
Edit2: Answer :
New FsCounter template looks like this:
<div fs-counter-dynamic value="item._quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="item._quantity"
name="quantity" id="quantity{[{ item.getId() }]}"
></div>
Do you know ng-repeat, then you don't have such problems
<div ng-repeat="product in products">
<fs-counter index="product.index" value="product.value"></fs-counter>
</div>
and in your controller
$scope.products = [
{index:1, value:"Cola"},
{index:2,,value:"Fanta"}
]
to remove an element you just have to do
$scope.products.splice(0,1);
Edit:
I suggest to save all necessary data inside the item you use inside ng-repeat. Your problem is, that you mix data from array with other data from your $scope. It is possible to $watch changes in your directive, but if you set them with ng-repeat everything is done automatically.
$scope.products = [
{index:1, name:"Cola", price:1.50, image:"your/path.png", attributes:{...}},
{index:2, name:"Fanta", price:1.40, image:"your/path.png"}
]
And then in your html
<div class="unItem" ng-repeat="item in ngCart.products track by $index">
<div class="photo"><img ng-src="item.image" alt=""></div>
<div class="details">
<h3>{{item.name}} <span>{{item.price | currency}}</span></h3>
</div>
<div class="quantity">
<div fs-counter-dynamic value="item.quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="item.index"
data-editable
ng-model="item.quantity"
name="quantity" id="{{'quantity-' + $index}}",
index="item.index"
></div>
</div>
<div class="total">Total : {{ item.price * item.quantity | currency }}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.index);"></a></div>
</div>

Scope variable not updating in view despite $scope.$digest

.controller('PizzaCtrl', ['$scope','$state','$ionicLoading',
function($scope, $state, $ionicLoading) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$scope.items = $scope.$parent.menu.pizze;
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
The data all checks out. The correct item information is logged to the console. However, the ng-repeat="item in items" directive in my view does not update with the pizza items.
I tried using $scope.$apply and $scope.$digest inside the event listener, but the console threw an error saying the digest was already in progress.
Also worth noting that this controller has two sibling controllers that have identical logic to this one, except for different sections of the menu. The console.log("pizza") statement isn't executed until I click into the state.
Is there a clear reason why my view is not updating?
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()">
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>
Instead of using $scope.$apply try to use $timeout angular service.
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
.controller('PizzaCtrl', ['$scope','$state','$ionicLoading', '$timeout'
function($scope, $state, $ionicLoading, $timeout) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$timeout(function(){
$scope.items = $scope.$parent.menu.pizze;
});
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
Turns out the solution to this problem is that I needed to add a missing closing tag to the <ion-refresher> tag.
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()"></ion-refresher>
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>

AngularJS dirPaginate Only Filtering current page

I have included dir paginate into my angularjs project to handle pagination easily, it works well up until the point where I want to filter all the pages results.
The controller that contains the data and filter looks like so:
app.controller('listCtrl', function ($scope, services) {
$scope.sort = function(keyname){
$scope.sortKey = keyname; //set the sortKey to the param passed
$scope.reverse = !$scope.reverse; //if true make it false and vice versa
}
$scope.currentPage = 1;
$scope.pageSize = 10;
services.getPosts().then(function(data){
$scope.posts = data.data;
});
});
The dir-paginate looks like so
<div dir-paginate="data in posts | itemsPerPage:5 | orderBy:sortKey:reverse" class="col-xs-12 post">
<div id="title" class="col-xs-3"></div>
<div id="title" class="col-xs-9"></div>
<div id="poster" class="col-xs-3">
<img src="pics/house.jpg" id="avatar">
<a ng-if="data.user_name == '<?php echo $currentUser?>'" href="edit-post/{{data.post_id}}"> Edit </a>
</div>
<div class="col-xs-9">
<div class="col-xs-12">
<p class="timestamp">{{ data.post_datetime }}</p>
</div>
<div id="rant">
<span style="word-wrap: break-word;">{{ data.post_content }}</span>
</div>
<div id="stats" class="col-xs-12"> <img src="pics/comment.png"><span>3</span>
</div>
</div>
</div>
<dir-pagination-controls
max-size="5"
direction-links="true"
boundary-links="true" >
</dir-pagination-controls>
I call the sort function like so:
<button id="changeLikes" ng-click="sort('post_id')">ID</button>
It only sorts the current page of Data, if i use the pagination and click page 2 it will then only filter page 2 and so on, any ideas here would be of great help getPosts() returns post data via php with no group or order by clauses in the sql that would affect the angular.
Have you tried flipping the order of the filters, i.e.
<div dir-paginate="data in posts | orderBy:sortKey:reverse | itemsPerPage:5" class="col-xs-12 post">
The dir-paginate docs state:
itemsPerPage: The expression must include this filter. It is required
by the pagination logic. The syntax is the same as any filter:
itemsPerPage: 10, or you can also bind it to a property of the $scope:
itemsPerPage: pageSize. Note: This filter should come after any other
filters in order to work as expected. A safe rule is to always put it
at the end of the expression.

Categories