Ionic (angularjs) ion-slide-box with ng-repeat not updating - angularjs

I have an ion-slide-box using ng-repeat showing images that are fetched from a remote server.
<ion-slide-box on-slide-changed="slideChanged(index)" auto-play="true" does-continue="true">
<ion-slide ng-repeat="slide in files">
<img ng-src="{{slide.filename}}" >
</ion-slide>
</ion-slide-box>
and the js:
$scope.getFiles = function() {
Files.query({
//some parameters
}).$promise.then(function(data) {
$scope.files = data;
});
When getFiles() is called (which fires a service that is not shown here), $scope.files is updated successfully. But the DOM (the images loaded into the ion-slide-box) are not automatically updated. How do I refresh the DOM?? I have tried timeout (nothing happens) and $scope.$apply (throws error that $dispatch already sterted.)
Even though this is a mobile app, when testing in desktop browser, I can re-size the browser and the images automatically display. So either I figure out how to keep this updated, or find a better image slider/carousel to use.

You need to call the $ionicSlideBoxDelegate.update() method after you have changed the scope variable. The slidebox does not automatically listen for changes in the latest version of the framework.
http://ionicframework.com/docs/nightly/api/service/$ionicSlideBoxDelegate/
You should set a delegate-handle attribute and use that to make sure you don't interfere with any other slideboxes you make have loaded at the time.
html
<ion-slide-box delegate-handle="image-viewer" on-slide-changed="slideChanged(index)" auto-play="true" does-continue="true">
<ion-slide ng-repeat="slide in files">
<img ng-src="{{slide.filename}}" >
</ion-slide>
</ion-slide-box>
js
$scope.getFiles = function() {
Files.query({
//some parameters
}).$promise.then(function(data) {
$scope.files = data;
$ionicSlideBoxDelegate.$getByHandle('image-viewer').update();
});
}
I believe this will change in the near future so your code will work as expected. They are working on an updated slidebox component but it was pulled at the last minute to speed up the latest release.

You need to wait the ng-repeat is rendered before being able to update() your slidebox, use this directive :
angular.module('starter')
.directive('repeatDone', function () {
return function (scope, element, attrs) {
if (scope.$last) { // all are rendered
scope.$eval(attrs.repeatDone);
}
}
})
HTML :
<ion-slide-box>
<ion-slide ng-repeat="day in week" repeat-done="repeatDone()" >
In your controller
$scope.getFiles = function() {
Files.query({
//some parameters
}).$promise.then(function(data) {
$scope.week = data;
});
$scope.repeatDone = function() {
$ionicSlideBoxDelegate.update();
//$ionicSlideBoxDelegate.slide($scope.week.length - 1, 1);
};

To work properly must do the following .
After 8 hours of testing and achieve internet search to find the right solution.
<ion-slide-box on-slide-changed="slideChanged(index)" auto-play="true" does-continue="true">
<ion-slide ng-repeat="slide in files">
<img ng-src="{{slide.filename}}" >
</ion-slide>
</ion-slide-box>
delegate-handle="image-viewer" does not have to be in ion-slide-box must be in <ion-content delegate-handle="mainhanddle">
and from the controller must execute these two commands:
$ionicSlideBoxDelegate.update();
$ionicSlideBoxDelegate.$getByHandle('mainhanddle').update();
At the end of each call.
To add this here, <ion-slide-box delegate-handle="image-viewer" other important properties such as cancel and for example nr-repeat does not work properly.
Well, I hope I have helped , if you need more details please do not hesitate to contact me.

Instead of:
$scope.files = data;
Try:
$scope.files.splice(0,$scope.files.length);
$scope.files = $scope.files.concat(data);
That should allow you to update the content of the array without losing the initial memory reference.
Note: I am assuming that $scope.files and data are arrays.

I achieved to resolve this trouble using the library ion-image-lazy-load. Try out with this approach:
JS:
.controller('myCtrl', function ($scope) {
$scope.images = imagesArray(); // Get array of objects with image urls
$scope.slider = {
options: {
direction: 'horizontal',
slidesPerView: '1',
grabCursor: true
}
};
});
View:
<ion-content lazy-scroll>
<ion-slide-box style="width:100% !important">
<ion-slide ng-repeat="slide in images">
<img image-lazy-src="{{slide.url}}" lazy-scroll-resize="true" image-lazy-loader="lines" style="width:100%">
</ion-slide>
</ion-slide-box>
</ion-content>

Related

Img src doesn't change when i updated the source

I have an ionic 1 app and im trying to dynamically change a img source. I thought all i had to do whas update the scope linked to this source, but it doesn't worked. Any idea on what might be wrong?
my View
<div ng-if="isList" class="item style-list" ng-repeat="(key, item) in items"
ng-click="goTo(item)">
<div class="img-container" ng-if="isList">
<m-img encode="true" src="item.image"></m-img>
</div>
<h1 ng-bind="item.title"></h1>
<p ng-bind="item.resume" ng-if="item.resume"></p>
<p ng-bind-html="stripHtml(item.description) | mCut:100" ng-if="!item.resume"></p>
</div>
my Controller
$scope.$on("update-data", function(event, args) {
$scope.items[1].description =
args.response.results[0].item.description;
$scope.items[1].id = args.response.results[0].item.id;
$scope.items[1].image = args.response.results[0].item.image;
$scope.items[1].resume = args.response.results[0].item.resume;
$scope.items[1].title = args.response.results[0].item.title;
});
My m-img component
html
<div class="thumb-size notloaded">
<div class="thumb" ng-if="imgStyle" ion-img-cache-bg ng-
style="imgStyle">
</div>
</div>
My m-img JS is kinda extensive, here https://codeshare.io/adABMe
it seems that the error is inside mImg component, from the code here you're not watching changes on src attribute (but you're doing it only on url attribute that here is not used) and you're triggering some logic (defined in $scope.load) to update the view.
You should add a watcher even on src and trigger your load method to update $scope.imgSrc variable , this should update your view consequently
controller: function($scope, $timeout, $mAppDef) {
$scope.$watch('src', function() {
$scope.load()
});
}

uib-carousel has height 0

Using:
angularjs 1.5.3
ui-bootstrap 1.3.2
This is my template:
<div id="content" class="center">
<div id="title">
<uib-carousel active="true" interval="5000" ng-if="slides && slides.length">
<uib-slide ng-repeat="slide in slides track by slide.name">
<img ng-src="{{ slide.image }}" />
<div class="carousel-caption">
<p>{{ slide.description }}</p>
</div>
</uib-slide>
</uib-carousel>
</div>
</div>
Somewhere in my code I perform a call (with a working service I used many times) like this:
Index.controller('Index.Main', ['$scope', '$sce', 'MyService', function($scope, $sce, myService) {
$scope.slides = [];
myService.loadStuff($scope, ..., function(){
console.log($scope.slides);
}, function(data, status) {
console.log('Could not retrieve stuff');
}).update();
}]);
Which can be detailed as follows:
MyServices calls $http.get behind the scenes.
An array of slides will be shown by console when there is an http success. Right now this is the case.
An error message will be shown by console when there is an http error. Right not it is NOT the case.
Finally, the images are attached correctly to the dom. I can be sure about this because I can inspect it in my browser. however the carousel height (and contents height) is 0.
What am I missing? How can I fix it?
I actually forgot to correctly specify the index property. If there is no index to match against, then there is no way to tell which image is active. When no image is active, the container has height 0
From uibootstrap doc:
As you can see, you need to declare the height of the carousel in the container div

Add message after ng-if removes all elements

Here's what I'm trying to do. I'm building an app using ionic, angular and I have two tabs with lists of questions that users had entered. Once a question is answered on the first tab (tab a) it gets hidden using ng-if because a property gets added called 'conversation' so the question moves from one tab to the other via it being hidden in one and shown in the other. This all works fine.
However, once there's no question left because they've all been hidden in tab a, i'd like to show a message. This has been the most exhausting trial an error (mostly error) i've ever done. i've different directive configurations and, most recently, a custom filter, nothing seems to work perfectly.
Here's some experimental code. I tried listening on the tab. (this is just one try. I also tried custom filter and another configuration of this directive)
//template
<ion-view view-title="Questions">
<ion-content>
<ion-list ng-show="rooms" class="display-text">
<ion-item class="item-icon-left questions" ng-repeat="room in rooms" ng-if="!room.conversation" type="item-text-wrap" ng-click="openChatRoom(room.schoolid, room.$id, room.userid, room.question)">
<i class="icon {{room.icon}}"></i>
<h2>{{room.question}}</h2>
</ion-item>
</ion-list>
<div class="msg"></div>
<ion-list ng-hide="rooms.length">
<ion-item class="textCenter">
<i class="icon ion-loading-c"></i> Loading Rooms
</ion-item>
</ion-list>
</ion-content>
restrict: 'C',
link: function(scope, elem, attrs) {
scope.$watch('tabs', function (){
var myEl = angular.element( document.querySelector('.questions'));
if(scope.tabs == 'ctrl1'){
if(!myEl){
var temp = $compile('<ion-item class="textCenter"><i>No conversations<i></ion-item>');
var content = temp(scope);
elem.find('div').append(content);
}
}
if(scope.tabs == 'ctrl2'){
if(!myEl){
var temp = $compile('<ion-item class="textCenter"><i>No current questions<i></ion-item>');
var content = temp(scope);
elem.find('div').append(content);
}
}
});
It looks like you are trying to write Angular as though it is jQuery. From what you've described you shouldn't need to manually append elements to the DOM like that, at least not for something as simple as this. Just put the message you want to display in the template and show/hide it based on how many questions you have left (your questions model).
Here's a possible solution just using Angular an ui.bootstrap for the tabs. This is just an example of a possible approach you might take and you will need to adjust it to your particular case but hopefully you will find it helpful.
They key point here is that you should be thinking in terms of adding/removing data from a model and dealing with basic display logic directly inside your template rather than manually adding/removing elements to the DOM a la jQuery.
DEMO
js
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('MainCtrl', function ($scope, $window) {
$scope.questions = [
{
content: 'What is the capital of Australia?',
answer: 'Canberra'
},
{
content: 'How many days are there in a week?',
answer: '7'
}
];
$scope.answeredQuestions = [];
$scope.userAnswer = {
value: ''
};
$scope.submitAnswer = function(){
var userAnswer = $scope.userAnswer.value,
// remove the fist question from the array
question = $scope.questions.shift();
question.userAnswer = userAnswer;
question.result = (question.answer === userAnswer);
// add the answered question object to the answeredQuestions array
$scope.answeredQuestions.push(question);
$scope.userAnswer.value = '';
};
});
html
<body ng-controller="MainCtrl">
<tabset>
<tab heading="Questions">
<p ng-hide="questions.length">There are no more questions</p>
<form ng-submit="submitAnswer()" ng-show="questions.length">
<p>{{questions[0].content}}</p>
<input type="text" ng-model="userAnswer.value">
<input type="submit" value="submit answer">
</form>
</tab>
<tab heading="Answered Questions">
<p ng-hide="answeredQuestions.length">There are no conversations</p>
<table ng-show="answeredQuestions.length" class="table table-striped">
<thead>
<tr>
<th>Question</th>
<th>User Answer</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="q in answeredQuestions">
<td>{{q.content}}</td>
<td>{{q.userAnswer}}</td>
<td>{{q.answer}}</td>
<td>{{q.result ? 'correct' : 'incorrect'}}</td>
</tr>
</tbody>
</table>
</tab>
</tabset>
</body>

Angular Google Maps inside accordion is getting blank first time

I'm using Angular Google Maps with bootstrap3 and html5. I'm using angular google maps inside accordion.
The problem is that when I write Angular Google Maps inside accordion it showed as blank first time but when I come back to this page (state)by browse another page (state) then I works as normal. If I don't use accordion then it works fine always.
Here is code the accordion that show blank first time.
<accordion close-others="true">
<accordion-group heading="Karta" is-open="map.open">
<div class="panel-body">
<div class="col-sm-12 col-xs-10 angular-google-map-container">
<ui-gmap-google-map center="localMap.center" zoom="localMap.zoom" events="localMap.events" draggable="true" refresh="localMap.refreshMap" ng-class="{'crosshair': waitingForInput}">
<ui-gmap-markers idKey="id" models="localMap.markers" coords="'self'" fit="'true'" options="'options'">
</ui-gmap-markers>
</ui-gmap-google-map>
</div>
</div>
</accordion-group>
</accordion>
And if remove accordion then it works all time.
<div class="panel-body">
<div class="col-sm-12 col-xs-10 angular-google-map-container">
<ui-gmap-google-map center="localMap.center" zoom="localMap.zoom" events="localMap.events" draggable="true" refresh="localMap.refreshMap" ng-class="{'crosshair': waitingForInput}">
<ui-gmap-markers idKey="id" models="localMap.markers" coords="'self'" fit="'true'" options="'options'">
</ui-gmap-markers>
</ui-gmap-google-map>
</div>
</div>
Please help me to solve this issue.
I had the same problem where I had a div with an ng-show directive that evaluated to false initially on view-load and the Google Maps map element would get initialized on view-load, too. But the div I passed to Google Maps api to initialize the map element was inside the ng-show='false' div and the blank map would show.
I solved it by changing the initializing of the map element to when the ng-show directive evaluated to true.
I think it would be best if you created the Google Maps map element, when the accordion got opened.
I had it all in a custom directive so it was easy to put a scope-watch inside the link method of the directive. Posting code for future users who might come across same problem.
angular.app('module').directive('customDirective', function ($timeout) {
return {
template: '...',
link: function(scope, element) {
var mapInitialized = false; # To track if map has already been initialized.
var mapOptions = {...};
var initMap = function() {
map = new google.maps.Map(element.find(".map-elem")[0], mapOptions);
mapInitialized = true;
};
scope.$watch 'isMapElemContainerShown', function(newValue, oldValue) {
if (newValue == oldValue) return;
if (newValue && !mapInitialized) {
$timeout(initMap, 100); # Allow delay for view element to update to be visible.
}
}
}
}
}
You can use the control object (http://angular-ui.github.io/angular-google-maps/#!/api/google-map)
<ui-gmap-google-map center='center' control='control'>
</ui-gmap-google-map>
In your controller, specify an empty control object (it gets initialized automatically). Use it to refresh the map when the accordion opens.
$scope.control = {};
$scope.$watch("map.open", function (newVal) {
if (newVal) {
$timeout(function () {
$scope.control.refresh(center);
});
}
});

How to start scroll list from the bottom?

I'm working on a chatapp and like most chatapps my app shows a list of messages.
The list is generated with ng-repeat.
I have reversed the messages so that the newest are at the bottom and
oldest at the top.
Currently when my app loads the list is scrolled down with
$ionicScrollDelegate
I don't like it doing it this way. It's not the correct way and sometimes this gives me some performance and loading issues when opening my app.
I was wondering if there is another, forced way, to start/render the list from the bottom to the top without the need for scrolling it to the bottom like I do now.
This is the code I currently use:
In my HTML:
<script type="text/ng-template" id="home.html">
<ion-view ng-controller="HomeController" title="Home">
<ion-content>
<ion-list>
<ion-item ng-repeat="message in messages track by $index">
{{message}}
</ion-item>
</ion-list>
</ion-content>
</ion-view>
</script>
In my app.js:
app.run(function($ionicPlatform, $state, $timeout, $localStorage, $location, $rootScope, $ionicScrollDelegate) {
document.addEventListener('deviceReady', function() {
setTimeout(function() {
$ionicScrollDelegate.scrollBottom(true);
}, 500);
});
})
Add a watch instead:
$scope.$watch('messages', function(newValue, oldValue) {
$ionicScrollDelegate.scrollBottom(true);
}, true);
The reason you are getting this behaviour is cause deviceReady does not guarantee that the items are rendered yet, which causes your list to not scroll anywhere at all. With a watch, you'll scroll when the array has been loaded and the values are already there.

Resources