I'm making a list of items and everytime that the user input some value it calls the listVendas function that searches immediately in the API, but this cause a bunch of requests, and some requests can finish before others, so my question is.
How can I abort a Promise so I can create a new one?
listVendas(event?: any) {
let codigovenda;
if (event) {
codigovenda = event.value;
}
if (typeof this.promise != 'undefined') {
// HOW DO I ABORT THE PREVIOUS PROMISE
}
this.promise = this.vendaProvider.getAll(codigovenda);
this.promise.then(data => {
this.vendas = data;
})
}
In Ionic you can use the rxjs switchMap operator and debounceTime operator to do this very easily.
example code
I've searched a lot, and you can't abort a promise, so my solution was to don't make the request everytime the user type something, instead of that I created a button to make the request
<form [formGroup]="searchForm" (ngSubmit)="listVendas()">
<ion-card class="search">
<ion-icon class="icon-search" name="search"></ion-icon>
<ion-input type="number" pattern="[0-9]*" value="" formControlName="codigovenda"></ion-input>
<ion-icon class="icon-camera" name="camera" (click)="scanBacode()"></ion-icon>
</ion-card>
<ion-row class="row-submit" justify-content-center>
<button ion-button full icon-start type="submit">
Buscar
</button>
</ion-row>
</form>
Related
Folks:
I have an ionic app that has a page where user can post comments on a story. If a user posts new comment, the comment is displayed in the page. Comments are fetched from a REST API using the "let comment of postComments" fragment with *ngFor.
<ion-content *ngIf="!searchUser" #myContent padding [scrollEvents]="true">
<ion-refresher slot="fixed" (ionRefresh)="reloadPage($event)">
<ion-refresher-content pullingIcon="arrow-dropdown" refreshingSpinner="circles" refreshingText="Refreshing..." >
</ion-refresher-content>
</ion-refresher>
<div class="post-content">
<ion-item lines="none" no-padding>
<ion-avatar slot="start">
<img [src]=" (profileUrl == null) ? 'assets/img/emptyperson.jpg' : profileUrl">
</ion-avatar>
<p class="user-post-text">
<strong>{{
username
}}</strong><span>
{{ description }}
</span>
</p>
</ion-item>
<div class="border"></div>
<ion-item-sliding class="comments-container" *ngFor="let comment of postComments; let i = index; let c = count;" [id]="'slidingItem' + i">
<ion-item no-padding lines="none">
<ion-avatar slot="start">
<img [src]=" (comment.profileUrl == null) ? 'assets/img/emptyperson.jpg' : comment.profileUrl">
</ion-avatar>
<p>
<strong>{{
comment.username
}}</strong>
{{ comment.comment }}
</p>
<ion-button
slot="end"
fill="clear"
(click)="likeButton(comment.id, comment.currentUserLike)"
[disabled]="disableLikeButton"
>
<ion-icon
*ngIf="!comment.currentUserLike"
slot="icon-only"
name="heart-outline"
color="black"
></ion-icon>
<ion-icon
*ngIf="comment.currentUserLike"
slot="icon-only"
name="heart"
color="danger"
></ion-icon>
</ion-button>
<br/>
</ion-item>
<ion-item-options *ngIf="isPostMine && (comment.userId != userdetails.id)">
<ion-item-option color="danger" >
<button color="danger" [disabled]="disableDeleteButton" (click)="removeComment(comment.id,i)"><ion-icon class="inside-icon" name="trash"></ion-icon></button>
</ion-item-option>
<ion-item-option color="primary" >
<button color="primary" (click)="reportOffensive(comment.id,i)"><ion-icon class="inside-icon" name="remove-circle"></ion-icon> </button>
</ion-item-option>
</ion-item-options>
<ion-item-options *ngIf="isPostMine && (comment.userId == userdetails.id)">
<ion-item-option color="danger" >
<button color="danger" [disabled]="disableDeleteButton" (click)="removeComment(comment.id,i)"><ion-icon class="inside-icon" name="trash"></ion-icon></button>
</ion-item-option>
</ion-item-options>
<ion-item-options *ngIf="!isPostMine && (comment.userId != userdetails.id)">
<ion-item-option color="primary" >
<button color="primary" (click)="reportOffensive(comment.id,i)"><ion-icon class="inside-icon" name="remove-circle"></ion-icon> </button>
</ion-item-option>
</ion-item-options>
<ion-item-options *ngIf="!isPostMine && (comment.userId == userdetails.id)">
<ion-item-option color="danger" >
<button color="danger" [disabled]="disableDeleteButton" (click)="removeComment(comment.id,i)"><ion-icon class="inside-icon" name="trash"></ion-icon></button>
</ion-item-option>
</ion-item-options>
<p class="all-comments">
<span class="grey-text"> {{ comment.createdAt | timeAgo }}</span>
<span *ngIf="comment.numLikes > 0" class="grey-text tab" (click)="userList(comment.id,'CommentLikes')">{{comment.numLikes}} likes</span>
</p>
</ion-item-sliding>
</div>
</ion-content>
<ion-footer>
<ion-item lines="none">
<ion-avatar slot="start">
<img src="{{ userProfileUrl }}" />
</ion-avatar>
<ion-textarea autocapitalize="sentences" maxLength="255" type="text"
[(ngModel)]="commentContent" (ionChange)="getUsers($event)"
rows="1"
placeholder="Add a comment"
></ion-textarea>
<ion-icon class="blueicon" slot="end" name="send" (click)="addComment()"></ion-icon>
</ion-item>
</ion-footer>
In my typescript, I have a function addComment() which refreshes the value of postComments so that the page automatically reloads.
addComment() {
if (this.commentContent.trim()) {
this.showLoader("Posting Comment ...");
this.userPostComment.comment = this.commentContent;
this.userPostCommentJSON = JSON.stringify(this.userPostComment);
//console.log("Comment JSON is "+this.userPostCommentJSON);
this.https.post(this.serviceUrl+'comments',this.userPostCommentJSON,this.httpOptions)
.subscribe(
(res: any)=>{
this.commentContent = "";
this.refresh();
setTimeout(() => {
console.log("In Timeout");
this.refreshPostComments("");
this.events.publish('postComment:created', {
id: res.id,
time: new Date()
});
this.hideLoader();
this.commentContent = "";
},2000)
},
err => {
this.hideLoader();
this.showAlert(err);
}
);
}
}
This is working like charm, when I get to the Comments page from the story.
Here is my problem. If I navigate to the Comments page from a oneSignal push notification, everything works fine up to the point where I enter a new comment. When I enter a new Comment, the comment is created in the database using the RESTAPI (works fine), the refreshPostComments method is called in typescript (works fine), but my view does not get updated till I physically click the comment box again.
I don't know what I am doing wrong. But in my app.component.ts, I check for oneSignal notification clicked and if clicked, I do this
if (localStorage.getItem('auth-user') != undefined || null) {
if (additionalData.type == "comment") {
await console.log("Going to Comments");
this.router1.navigateForward(['/comments', {
userId: parseInt(additionalData.userId),
username: additionalData.username,
userPostId: parseInt(additionalData.userPostId),
description: additionalData.description,
profileNameKey: additionalData.profileNameKey
}]);
}
That works. Takes me to the Comments Page. But once I am in the page, I can do virtually everything else like peach, except if I add a comment, then it does not reflect in real time.
Any pointers on what I am missing?
JR
Found my solution. I think Angular’s Change detection is triggered by certain events, and when I was navigating to the page from OneSignal's notification - whatever OneSignal was using, it was not getting on Angular's radar. So I had to import ChangeDetectorRef and do a manual detectChanges() to make sure ngFor would detect the changes as soon as they occur.
Phew. Spent almost a half a day on this. So much for OneSignal and Angular's quirks with OneSignal. And Ionic had nothing to do with it.
Regards,
JR
I am using ionic 3, and looping ion-card with like using ngFor. I want to know how can I react with the user when user click the like/unlike button in each ion-card without reload the list.
<ion-card *ngFor="let u of users">
<p>{{u.name}}</p>
<button ion-button [hidden]="u.isliked=='1'" (click)="like(u.id)">like</button>
<button ion-button [hidden]="u.isliked!='1'" (click)="unlike(u.id)">unlike</button>
</ion-card>
You can make use of the *ngIf operator. This won't hide the element like the hidden property, but actually removes the element from the DOM.
(made u.isLiked into a boolean because I think it's cleaner that way, personal preference. Also changed (click) to (tap), see the answer on ionic2 tap vs click for more details.)
<ion-card *ngFor="let u of users">
<p>{{u.name}}</p>
<button ion-button *ngIf="u.isLiked" (tap)="like(u.id)">like</button>
<button ion-button *ngIf="!u.isliked" (tap)="unlike(u.id)">unlike</button>
</ion-card>
And in your ts:
like(userId) {
for(let user of this.users) {
if(user.id == userId) {
user.isLiked = true;
}
}
}
unlike(userId) {
for(let user of this.users) {
if(user.id == userId) {
user.isLiked = false;
}
}
}
I am building an Ionic App for iOS & Android and so far I have a form where users can upload or take a photo and then fill in some other information and submit the form.
My issue:
Currently when the user goes to an Input to enter some extra text there is a go button at the bottom of the keyboard. If the user clicks this 'Go' button it tries to submit the form along with opening the choosePicture() function I have.
What happen when I click the
Go button - it opens the Library
The HTML
<ion-item class="file-upload">
<button class="btn btn-block" ion-button (click)="choosePhoto()">Choose a Photo</button>
<span class="sep"></span>
<button class="btn btn-block" ion-button (click)="takePicture()">Take a Picture</button>
<div class="uploaded-img">
<input type="file" value="" formControlName="hazardImage" hidden />
<span class="img-desc">Please Take or Upload an Image.</span>
<div class="img-picked" *ngIf="base64Img">
<img [src]="base64Img" alt="Hazard Picture" />
<!-- <img src="http://placehold.it/300x300" alt="Test Image" /> -->
</div>
</div>
</ion-item>
<small class="small-title">Where is the Hazard Location? (required)</small>
<ion-item class="input-box">
<ion-input type="text" formControlName="hazardLocation" [class.invalid]="!hazardForm.controls.hazardLocation.valid && (hazardForm.controls.hazardLocation.dirty || submitAttempt)"></ion-input>
</ion-item>
The TypeScript:
choosePhoto()
{
this.Camera.getPicture({
quality: 50,
destinationType: this.Camera.DestinationType.DATA_URL,
encodingType: this.Camera.EncodingType.JPEG,
sourceType: this.Camera.PictureSourceType.PHOTOLIBRARY,
allowEdit: true
}).then((ImageData) => {
// console.log(ImageData);
this.base64Img = ImageData;
this.hazardForm.patchValue({
hazardImage: this.base64Img
});
}, (err) => {
console.log(err);
})
}
I don't understand why the Go Button is opening the function for choosing a photo :/
You can modify the behaviour of the 'Go' keyboard button with:
#ViewChild('input-box') inputElm: ElementRef;
#HostListener('keyup', ['$event'])
keyEvent(e) {
if(!e.srcElement) return; //browser fix
var code = e.keyCode || e.which;
if (code === 13) {
if (e.srcElement.tagName === "INPUT") {
e.preventDefault();
//handle other behaviours
}
}
};
You have defined the <input> for "Hazard Location" as a Form since there is a formControlName="hazardLocation" attribute. This makes the Go or form submit button appear in the mobile keyboard.
Instead, you should have it as a normal input field like this
<input [(ngModel)]="hazardLocation">
I'm trying to scroll down to delegate-handle="start" with ionic on load. When i try to run it, i get this message in console.
Delegate for handle "small" could not find a corresponding element with delegate-handle="small"! scrollTop() was not called!
Possible cause: If you are calling scrollTop() immediately, and your element with delegate-handle="small" is a child of your controller, then your element may not be compiled yet. Put a $timeout around your call to scrollTop() and try again.
My code looks like this, can somebody maybe see what is wrong?
$timeout(function() {
$ionicScrollDelegate.$getByHandle('start').scrollTop();
}, 10);
If i use $ionicScrollDelegate.scrollBottom(), it will scroll to the bottom, so it must be a problem with the specific function.
The html code
<ion-view title="Kalender">
<ion-content>
<ion-list ng-repeat="activity in calendar">
<div class="item item-divider" ng-show="activity.date_divider != null" data-year="{{activity.year}}">
<div class="header-divider-small">{{activity.year}}</div>
<div class="header-divider">{{activity.date_divider}}</div>
</div>
<div class="item item-icon-right calendar" ng-show="activity.date_divider == null">
<span class="header">{{activity.name}}</span>
<br />
<span class="text">{{activity.time}} - {{activity.place}}</span>
<a class="button button-icon icon {{activity.icon}} right not-selected"></a>
</div>
</ion-list>
<div delegate-handle="start"></div>
</ion-content>
</ion-view>
Thanks.
Ionic 1.0.0-beta14 has some strange issue with getByHandle() so you can do it this way:
$timeout(function() {
var startHandle = _.find($ionicScrollDelegate._instances, function (s) {
return s.$$delegateHandle === "start";
});
startHandle.scrollTop();
});
Solution source at forum.ionicframework.com
I think there is a misunderstanding about what the delegate handle does here. Delegate handle is a way to name scroll/content containers. Think of it like a way to give your <ion-content> container a unique name that can be later used with the service. It is possible to have multiple <ion-content> containers on a single view, and this is why the naming is necessary. If you only have one scroll area or don't specify the handle, then it just uses the first view it finds.
You want to scroll to a particular place in the application, which is the job of $ionicScrollDelegate.anchorScroll('element-id');. Take a look at your code here with some modifications. I've put the delegate handle in the correct place, and then use the anchorScroll method to automatically scroll to that ID in the page.
Markup
<ion-view title="Kalender">
<ion-content delegate-handle="kalendar">
<ion-list ng-repeat="activity in calendar">
<div class="item item-divider" ng-show="activity.date_divider != null" data-year="{{activity.year}}">
<div class="header-divider-small">{{activity.year}}</div>
<div class="header-divider">{{activity.date_divider}}</div>
</div>
<div class="item item-icon-right calendar" ng-show="activity.date_divider == null">
<span class="header">{{activity.name}}</span>
<br />
<span class="text">{{activity.time}} - {{activity.place}}</span>
<a class="button button-icon icon {{activity.icon}} right not-selected"></a>
</div>
</ion-list>
<div id="start"></div>
</ion-content>
</ion-view>
Controller
$timeout(function() {
$ionicScrollDelegate.$getByHandle('kalendar').anchorScroll('start');
}, 10);
Specify the dalegate-handler in ion-content
<ion-view title="Kalender">
<ion-content delegate-handle="start">
<ion-list ng-repeat="activity in calendar">
<div class="item item-divider" ng-show="activity.date_divider != null" data-year="{{activity.year}}">
<div class="header-divider-small">{{activity.year}}</div>
<div class="header-divider">{{activity.date_divider}}</div>
</div>
<div class="item item-icon-right calendar" ng-show="activity.date_divider == null">
<span class="header">{{activity.name}}</span>
<br />
<span class="text">{{activity.time}} - {{activity.place}}</span>
<a class="button button-icon icon {{activity.icon}} right not-selected"></a>
</div>
</ion-list>
</ion-content>
</ion-view>
Scroll top can be achieved by forgetting the scroll position which can be achieved by using
$scope.$on("$destroy", function() {
var delegate = $ionicScrollDelegate.$getByHandle('start');
delegate. forgetScrollPosition();
});
Use
$ionicScrollDelegate.$getByHandle('start').scrollTop(); //To scroll to Top.
As I noticed delegate-handle won't work if used with overflow-scroll.
I managed to do it this way:
$scope.scrollHandle = some-handle-value-that-you-want-to-use;
$scope.$on('$ionicView.loaded', function () {
$timeout(function () {
scrollView = $ionicScrollDelegate._instances.filter(function (s) {
if (!s.$$delegateHandle) return false;
return $parse(s.$$delegateHandle.slice(2, -2))
(angular.element(s.element).scope()) == $scope.scrollHandle;
})[0];
}).then(function () {
scrollView.scrollTo(0, 0, false);
});
});
In the template:
delegate-handle="{{scrollHandle}}"
EDIT:
This doesn't work anymore, check this answer for new solution: https://stackoverflow.com/a/32123613/1630623
This is working:
view.html
<div><input type="radio" name="radioPriority" data-bind="checked: priority" value="want" style="margin-top: -3px; margin-right: 3px;" />I want to</div>
<div><input type="radio" name="radioPriority" data-bind="checked: priority" value="need" style="margin-top: -3px; margin-right: 3px;"/>I need to</div>
controller.js
function feedbackViewModel() {
var self = this;
self.priority = ko.observable("want");
//lots of other stuff
}
As expected, when you select the second radio the priority observable's value changes to "need". However, I'd like to use a Twitter Bootstrap button group as a radio. Here is the HTML I have, but it does not change the observable as expected:
<span class="btn-group" data-toggle="buttons-radio">
<button data-bind="checked: priority" type="button" class="btn" value="want">I want to</button>
<button data-bind="checked: priority" type="button" class="btn" value="need">I need to</button>
</span>
update I have a jsfiddle going here: http://jsfiddle.net/a8wa8/6/ "priority1" is the standard radio selects and "priority2" is the bootstrap buttons.
The issue is that you are using Checked binding with button which is not allowed, instead you can use click binding. check this fiddle:
http://jsfiddle.net/a8wa8/7/
Updated:
Yes you can achieve this by using ko css binding. Check this updated fiddle:
Updated Fiddle
The checked binding only works with "checkable" controls like checkbox (<input type='checkbox'>) or a radio button (<input type='radio'>).
But you have button in your second example where bootstrap just emulates the look of the radio button group so the checked binding doesn't work.
However you can use the click binding where you pass the value to your observable:
<span class="btn-group" data-toggle="buttons-radio">
<button data-bind="click: function() { priority2('want') }"
type="button" class="btn" value="want">I want to</button>
<button data-bind="click: function() { priority2('need') }"
type="button" class="btn" value="need">I need to</button>
</span>
And you can hide this behind a custom binding:
ko.bindingHandlers.valueClick = {
init: function(element, valueAccessor, allBindingsAccessor,
viewModel, bindingContext) {
var value = valueAccessor();
var newValueAccessor = function() {
return function() {
value(element.value);
};
};
ko.bindingHandlers.click.init(element, newValueAccessor,
allBindingsAccessor, viewModel, bindingContext);
},
}
Then you can use it like:
<span class="btn-group" data-toggle="buttons-radio">
<button data-bind="valueClick: priority2"
type="button" class="btn" value="want">I want to</button>
<button data-bind="valueClick: priority2"
type="button" class="btn" value="need">I need to</button>
</span>
Demo JSFiddle.