Filter in ng-repeat not applying - angularjs

I'm working on a Ionic project which has a view that shows a scrollable list with a search input that filters the displayed results, this view is located in a template that is loaded into another view.
We noticed the search input would move with the content when the scrolling began, disappearing making it a problem when you had scrolled and wish to filter the results.
I tried to workaround this by pulling the search input outside the template and placing it above where it is loaded in the parent view.
While this renders the search input at a fixed location, even though I specified the controller that handles its logic, the filter isn't applied on the list.
Here is how my code looks on the parent view:
<ion-popover-view id="popfecha" class="fit" >
<ion-view view-title="Nuevo ReSAT">
<ion-content class="padding" data-ng-hide="activity.state.notes || activity.state.reasonShow || activity.state.product_lineShow || activity.state.account || activity.state.contact " >
<form name="form" data-ng-submit="submitForm()">
<!-- A long form -->
</form>
</ion-content>
<ion-view class="wrapper" data-ng-show="activity.state.account" >
<!-- Filter section -->
<div data-ng-controller="AccountCatalogueCtrl" class="item item-input filterSearch bar-header popupBorder borderFirstItemPopup">
<label class="item-input-wrapper">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Search" class="form-control"
ng-model="filterText.name">
</label>
</div>
<!-- ./Filter section -->
<ion-content class="padding" data-ng-show="activity.state.account" >
<div data-ng-controller="AccountCatalogueCtrl" ng-include="'templates/account-catalogue.html'"></div>
</ion-content>
</ion-view>
<ion-content class="padding" data-ng-show="activity.state.notes" >
<div data-ng-controller="ActivityNotesCtrl" ng-include="'templates/activity-notes.html'"></div>
</ion-content>
<ion-content class="padding" data-ng-show="activity.state.reasonShow" >
<div data-ng-controller="ReasonCatalogueCtrl" ng-include="'templates/reason-catalogue.html'"></div>
</ion-content>
<ion-content class="padding" data-ng-show="activity.state.product_lineShow" >
<div data-ng-controller="ProductLineCatalogueCtrl" ng-include="'templates/product-line-catalogue.html'"></div>
</ion-content>
<ion-content class="padding" data-ng-show="activity.state.contact" >
<div data-ng-controller="ContactCatalogueCtrl" ng-include="'templates/contact-catalogue.html'"></div>
</ion-content>
</ion-view>
</ion-popover-view>
As you can see, I'm specifying the controller AccountCatalogueCtrl to the div that holds the search input and to the div that loads the template account-catalogue.html.
Here's my template:
<ion-list>
<ion-radio class="item-avatar popupBorder borderLastItemPopupIterator whiteBackground" ng-repeat="acc in accountCatalogue | orderBy:'-name' : true | filter:filterText track by acc._id"
type="item-text-wrap" ng-value="acc.name" ng-if="!acc.isDivider" ng-click="select(acc); go()" >
<img src="img/account.png">
<h2>{{acc.name}}</h2>
</ion-radio>
</ion-list>
<ion-infinite-scroll
ng-if="canLoadAccounts"
on-infinite="loadAccounts()"
distance="5%">
</ion-infinite-scroll>
I even placed a watch on the filterText object to see it was being updated, the watch did show me that the property name was changing.
This is my controller:
var mod = angular.module('starter.controllers');
mod.controller('AccountCatalogueCtrl', ['$scope', '$rootScope', '$location', '$timeout', function ($scope, $rootScope, $location, $timeout) {
$scope.select = function(acct){
$rootScope.activity.accountId = acct._id;
};
$scope.go = function ( ) {
$timeout(function(){
$scope.$emit('HideAccount');
},150);
};
// Added the lines below to see if the filter was changing
// Since in the beginning filterText doesn't exist undefined is returned
// Later when a value is placed in the input then the filterText object is created
$scope.$watch('filterText.name', function(n, o){
console.log('filter updated ' + JSON.stringify(n) + ' ' + JSON.stringify(o));
});
}]);
If I place the search input inside my template, the filter is applied, but when placed outside the filter isn't applied. Am I doing something wrong in my implementation? How can I make sure the filter applies even if the component that received it is outside the template that makes use of the filter?

I solved this issue by removing the controller from the search input, so it looked like this:
<!-- Filter section -->
<div class="item item-input filterSearch bar-header popupBorder borderFirstItemPopup">
<label class="item-input-wrapper">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Search" class="form-control"
ng-model="filterText.name">
</label>
</div>
<!-- ./Filter section -->
I saw that I could access to filterText on my controller AccountCatalogueCtrl due to the component that shows the catalogue has access to the scope variables from the parent component that it is holding it. This way I could change the value outside AccountCatalogueCtrl and affect the filter inside the list.

Related

Angular doesn't set databinding

I have a problem with Angular, it seems to not do the two way binding.
I'm pretty new on this stuff, so I might just look over something.
Here is my code.
View:
<ion-view view-title="Update challenge">
<ion-content>
<ion-list>
<ion-item>
Current total
<span class="item-note">
{{challengeProgress.current_total_reps}}
</span>
</ion-item>
<ion-item>
Ultimate goal
<span class="item-note">
{{challengeProgress.total_reps}}
</span>
</ion-item>
<ion-item>
Todays goal
<span class="item-note">
{{todaysReps}}
</span>
</ion-item>
<ion-item>
Left for today
</ion-item>
<ion-item>
<label class="item item-input">
<input type="text" placeholder="Performed reps" ng-model="reps">
</label>
</ion-item>
<div class="button button-calm button-block" ng-click="updateProgress()">Update!</div>
Reps {{reps}}
</ion-list>
Controller:
$scope.reps;
$scope.updateProgress = function(reps){
console.log(reps);
SendToAPI.updateChallenge(u_id, c_id, toAdd);
}
reps seems to be undefined and the {{reps}} doesn't get updated either.
There is no need to pass reps as parameter.You can have access in the $scope.updateProgress function as $scope.reps.
HTML :
<div class="button button-calm button-block" ng-click="updateProgress()">Update!</div>
JS :
$scope.updateProgress = function(){
console.log($scope.reps);
//SendToAPI.updateChallenge(u_id, c_id, toAdd);
}
Please check Plunker
This appears to be a combination of issues related to the ionic framework.
The first issue is that ion-item elements actually create a child scope, and because reps is a primitive rather than an object, it isn't visible in other scopes due to prototype inheritance. This is easily fixed by ensuring that the reps is inside the same ion-item as the function that will be consuming it, though it could also be solved by making an object on $scope, $scope.workout.reps for example, that does not have the same issues with inheritance.
The second issue seems to be that the function itself is never actually firing. On the surface, this appears to be some sort of issue with the CSS in ionic, but it is easily fixed by changing the div to an ion-item instead.
The following shows the working changes to the view:
<ion-item>
<label class="item item-input">
<input type="text" placeholder="Performed reps" ng-model="reps">
</label>
<ion-item class="button button-calm button-block"
ng-click="updateProgress(reps)">Update!</ion-item>
Reps {{reps}}
</ion-item>
http://codepen.io/Claies/pen/EPEBZN
Note in the codepen, I log both the passed in reps and $scope.reps, to prove that there is an inheritance issue.
Bas dont seem like you have 1. declared an app and 2. wrapped your html with an ng-controller. Without a controller there is no link between you HTML and your controller. As you can see with 2 way binding there is no need for a ng-click as it is updated into HTML as well as your controller
Here is a basic working example of your code:
https://plnkr.co/edit/w2B7iRcaoTiOCdL1JJTX?p=preview
<!DOCTYPE html>
<html ng-app="plunker">
<head>
</head>
<body ng-controller="MainCtrl">
<h1>Hello Plunker!</h1>
<label class="item item-input">Label</label>
<input type="text" placeholder="Performed reps" ng-model="name" />
// BUTTON NOT NEEDED for update but can used for some other event
<button class="button button-calm button-block" ng-click="updateProgress()">Update!</button>
<p>Hello {{name}}!</p>
</div>
</body>
</html>
Script:
(function(angular) {
'use strict';
var myApp = angular.module('myApp',[]);
myApp.controller('GreetingController', ['$scope', function($scope) {
$scope.name = "Hello World";
$scope.updateProgress = function(val){
console.log(reps);
$scope.name = val;
};
}]);
})(window.angular);

Get value from ng-repeat outside of ng-repeat

I got a header bar outside of ng-repeat:
<ion-header-bar class="headerView" ng-show="!hideAll">
<button id="shareImage" class="button button-icon icon ion-share" style="color:#fff" ng-click="shareImageTo({{originalImageSource}})"></button>
<button class="button button-outline button-light close-btn" ng-click="closeModal()">{{::actionLabel}}</button>
</ion-header-bar>
And a slidebox where images are shown:
<ion-slide ng-repeat="single in slides track by $index">
<div class="item item-image gallery-slide-view">
<img ng-src="{{single.original}}">
<input type="text" ng-model="originalImageSource" value="{{single.original}}">
</div>
</ion-slide>
{{single.original}} is an URL to an image. But this URL is not placed in the input field. It is shown when I´m deleting this ng-model statement.
If a user clicks the button #shareImage the ShareImageTo(URL) function is executed. But with an undefined value for {{originalImageSource}}.
Any thoughts on how i could pass the URL in {{single.original}} to this ShareImageTo() function?
With $ionicSlideBoxDelegate you can get the current slide index via currentIndex(). That enables you to get the URL by $scope.slides[$ionicSlideBoxDelegate.currentIndex()].original in your ShareImageTo()-method like that:
$scope.ShareImageTo = function() {
var URL = $scope.slides[$ionicSlideBoxDelegate.currentIndex()].original;
//your logic
};
Another approach would be to use ion-slide-box's directive on-slide-changed.
Template:
<ion-slide-box on-slide-changed="slideHasChanged($index)">
...
</ion-slide-box>
Controller:
$scope.slideHasChanged = function(index) {
$scope.originalImageSource = $scope.slides[index].original;
//.. some more logic
}
Just create a div that wrap around both your navbar and your slidebox, and place your ng-repeat in that div :)

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!

How to show div when link is clicked with ng-show angular js

first off, i have links from a json and display it using ng-repeat and i want to know if its best practise to load all object property (in a div) corresponding to its link and hide it so when you click a link, the corresponding div shows.
I'm thinking about the worst case scenario, were we have about a 100 links and the load all corresponding divs and hide it o_O
So this is my html:
<a href="">
<div ng-model="showModal" class="contentItem" ng-repeat='meal in recipes | filter:searchText' ng-style="{'background-image':'url({{ meal.url }})'}">
<span id="contentItemHeader">{{ meal.title }}</span>
<span id="contentItemLevel">{{ meal.level }}</span>
</div>
</a>
This is the div (modal) i want to display in full screen:
<div ng-show="showModal" ng-repeat='meal in recipes'>
<span>{{ meal.url }}</span>
<span>{{ meal.method }}</span>
<span>{{ meal.ingredients }}</span>
</div>
Use case is:
-all links loads
-click a link
-corresponding div shows
Thats it ! just like when you see a list of videos on youtube, you click one, then the video page opens but as a modal (same page)
Thanks alot,
regards
You can utilize $modal service from the UI Bootstrap, define template for meal's modal and attach ng-click handler on each individual meal in ng-repeat. In click handle you can open modal and pass meal instance to modal's scope:
View:
<div ng-repeat='meal in recipes' ng-click='selectMeal(meal)'>
{{meal.title}}
</div>
Conrtoller:
$scope.selectMeal = function(meal) {
var dialogScope = $scope.$new(true);
dialogScope.meal = meal;
var modalInstannce = $modal.open({
templateUrl: 'meal-dialog.html',
scope: dialogScope
});
};
Modal template:
<div class="modal-header">
<h3 class="modal-title">{{ meal.title }}</h3>
</div>
<div class="modal-body">
<div>{{ meal.url }}</div>
<div>{{ meal.method }}</div>
<hr />
<h4>Ingridients</h4>
<ul >
<li ng-repeat='ingridient in meal.ingridients' >{{ingridient.name}} : {{ingridient.amount}}</li>
</ul>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="$close()">Close</button>
</div>
Plunker here
To show div in question, when meal selected, introduce new selectedMeal property in $scope and use it in modal template, remove ng-repeat from modal div and set selectedMeal and showModal in selectMeal function:
$scope.selectMeal = function(meal) {
$scope.selectedMeal = meal;
$scope.showModal = true;
};
<div ng-show="showModal" >
<span>{{ selectedMeal.url }}</span>
<span>{{ selectedMeal.method }}</span>
<span>{{ selectedMeal.ingredients }}</span>
</div>
if your worried about performance of having all the divs exist, but hidden, consider using ng-if instead of ng-show. ng-if does not create the dom element if the expression is false.
<div ng-if="expression">
//element exists if 'expression' is true
</div>
if you are ok to ahead with angular-ui then i think the accordian fits the requirement perfectly... [here's the link for angular-ui's the accordian] directive 1
angular-ui is the pure angularjs implementation of all twitter bootstrap components.
<accordion close-others="oneAtATime">
<accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
This content is straight in the template.
</accordion-group>
<accordion-group heading="{{group.title}}" ng-repeat="group in groups">
{{group.content}}
</accordion-group>
</accordian>
you can have either one or all user selected link detail section open at a time...

Angular UI-Bootstrap 0.7.0 Collapse

I'm using the collapse feature of UI-Bootstrap (http://angular-ui.github.io/bootstrap/#/collapse) but when I use the collapse/toggle it loses the data and just returns "true".
Here is a Plunker to show my issue, http://plnkr.co/edit/e689Wureay8AMZQ9IIno?p=preview
A snippet from the code:
<div ng-repeat="name in names | filter:radioModel:true">
<span ng-model="namesList" ng-click="name = !name">Toggle collapse {{name.firstName}}</span>
<hr>
<div ng-show="name">
<div class="well well-large">{{name}} -</div>
</div>
That is because you are overwriting name in the ng-click here: ng-click="name = !name". That will make name true or false, depending on how many times ng-click was invoked.
What you want is to toggle a property on the model to collapse/uncollapse the following detail:
<div ng-repeat="name in names | filter:radioModel:true" >
<!-- change the property name.collapsed -->
<span ng-model="namesList" ng-click="name.collapsed = !name.collapsed">Toggle collapse {{name.firstName}}</span>
<hr>
<!-- use ng-hide to "collapse" -->
<div ng-hide="!!name.collapsed">
<div class="well well-large">{{name}} -</div>
</div>
</div>
Working demo: http://plnkr.co/edit/at7ceCYDXwWWlqUlKnkQ?p=preview

Resources