Model update not reflected in UI only on second NavController page - ionic-framework

I have a bizarre problem that when I change a value in the model, it does not update the view. My demo is a simple page which displays a timer whose value is updated in the model which I want reflected in the UI:
import { Component } from '#angular/core';
import { Observable } from 'rxjs/Rx';
#Component({
template: '<ion-content>Ticks (every second) : {{ticks}}</ion-content>',
})
export class ProgramOverviewPage {
ticks = 0;
ngOnInit() {
let timer = Observable.timer(0, 1000);
timer.subscribe(t => { this.ticks = t; console.log(t);});
}
}
If I set this page as my root page, it works fine. However, if I set a different page as my root page, and then immediately navigate to the timer page:
ngOnInit() {
this.nav.push(ProgramOverviewPage, {
});
}
then the page renders, but the tick value does not update the UI. I can't think of anything other than that the NavController is messing with the ChangeDetector, but I don't know why that would be. Anything I can add to debug this is much appreciated.
"ionic-angular": "2.0.0-beta.10"

Ionic 2 seems to be automatically setting Change Detection to OnPush for each of the Content objects (generated from <ion-content> I believe). This can be verified by using Augury and clicking on the Content object.
Because of this, it's necessary to explicitly tell the change detection system whenever you make any change which should be pushed to the UI using the ChangeDetectorRef.detectChanges() method. See the thoughtram blog for details.
import { Component, ChangeDetectorRef } from '#angular/core';
import { Observable } from 'rxjs/Rx';
#Component({
template: '<ion-content>Ticks (every second) : {{ticks}}</ion-content>',
})
export class ProgramOverviewPage {
ticks = 0;
ngOnInit() {
let timer = Observable.timer(0, 1000);
timer.subscribe(t => {
this.ticks = t;
console.log(t);
this.cd.detectChanges(); // Invoke the change detector
});
}
}

Related

How do you setup angular-bootstrap-toggle on an Angular 9 app?

I'm learning Angular 9 and have gone through the Tour of Heroes app and tutorial. I've used this tutorial as a base to add new features such as CRUD operations on a remote resource and I have added #ng-bootstrap/ng-bootstrap to the projects but I cannot get angular-bootstrap-toggle to work.
The instructions on Bootstrap Toggle don't match what I have learned so far and I can't find a solution anywhere.
For example, I don't know how this command angular.module('myApp', ['ui.toggle']); fits in with Angular 9 and the tutorial I have used.
How can I get the system to call onChange() when I click the toggle?
angular.json
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/bootstrap4-toggle/css/bootstrap4-toggle.min.css",
"src/styles.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.bundle.js",
"node_modules/bootstrap4-toggle/js/bootstrap4-toggle.min.js"
]
navigation-bar.component.html This displays correctly and toggles as expected
<input id="local-browse" (change)="onChange()" type="checkbox" checked="" data-toggle="toggle" data-on="Local" data-off="Remote" data-onstyle="success" data-offstyle="danger" data-size="sm">
navigation-bar.component.ts If I put in a standard checkbox the onChange() does work as expected but not with angular-bootstrap-toggle
import { Component, OnInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-navigation-bar',
templateUrl: './navigation-bar.component.html',
styleUrls: ['./navigation-bar.component.css'],
})
export class NavigationBarComponent implements OnInit {
localUrl = 'http://192.168.253.53';
remoteUrl = 'https://remoteaddress.com';
local = true;
constructor() {}
ngOnInit(): void {
$(document).ready(() => {
console.log('The document ready for jQuery!');
});
}
onChange() {
if (this.local === true) {
this.local = false;
} else {
this.local = true;
}
}
}
I dont think it is compatible with Angular 2+ (or at least angular 9).
You may like to use ng-toggle from https://www.npmjs.com/package/#nth-cloud/ng-toggle
Which is tested on Angular 9.
More information about installation in https://nth-cloud.github.io/ng-toggle/#/home

How to open a modal component from inside of another modal component without having a circular dependency?

I have two ngx-bootstrap modals created as a standalone components (not with template variables) - Login modal and Register modal. Each of the modals are have separate components which are located in my shared module and can be called from other modules. But the thing is that there is an option these modals to call each other - you can click a button from the login modal which has to bring you the Register modal and vice versa. When I try doing this using the BsModalService I get circular dependency errors since I have imported the login component in the register component and the register component in the login component.
I've tried to put this modal switching logic in a service with the hope that I won't get a circular dependency but it didn't help.
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { UserService } from 'src/app/core/services';
import { User } from 'src/app/core';
import { RegisterModalComponent } from '../register-modal/register-modal.component';
#Component({
selector: 'app-login-modal',
templateUrl: './login-modal.component.html',
styleUrls: ['./login-modal.component.css']
})
export class LoginModalComponent implements OnInit {
loginForm: FormGroup = this.fb.group({
// form definition
});
constructor(
public loginModalRef: BsModalRef,
private fb: FormBuilder,
private router: Router,
private user: UserService,
private modalService: BsModalService
) { }
ngOnInit() {
}
onSubmit() {
// form submit code ...
// hide the current modal
this.loginModalRef.hide();
}
openRegisterModal() {
// hide the current modal
this.loginModalRef.hide();
// open the new modal
this.modalService.show(RegisterModalComponent, {
animated: true,
class: 'modal-lg'
});
}
}
I have included only the code from the login modal since the situation on the other side is similar.
Just to mention that as a temporary solution I just made one modal component to serve the purpose as modal and I refactored the login and the register components to be like a regular components so I can include them inside the modal and switch them with ngIf depending on the parameters that I'm calling the modal with.

How to override hardware back button action in Ionic 3?

I want to know which function is called when we click ion-navbar back button by default in ionic 3.
I want to call the same function on hardware back button click.
You can use registerBackButtonAction of Platform Service.
You can override hardware back button action as below inside app.component.ts.
Remember to call registerBackButtonAction after Platform.ready().
import { Platform, App } from 'ionic-angular';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
constructor(public platform: Platform, private app: App) {
this.platform.ready().then(() => {
this.platform.registerBackButtonAction(() => {
let nav = this.app.getActiveNav()
if (nav.canGoBack()) {
// If there are pages in navigation stack go one page back
// You can change this according to your requirement
nav.pop();
} else {
// If there are no pages in navigation stack you can show a message to app user
console.log("You cannot go back");
// Or else you can exit from the app
this.platform.exitApp();
}
});
});
}
}
Hope this will help you.

Detect a click outside of an element

There is some components in Ionic that do not provide an event that is emitted when focus is lost.
For example ion-input provides ionBlur. On the other hand there is other elements like ion-content where I need to detect an outside click, but without knowing which event to use.
Is there a way to achieve that without being limited to the proposed events in the documentation?
I found this article that shows a way to use a custom directive to detect an outside click:
import {Directive, ElementRef, Output, EventEmitter, HostListener} from '#angular/core';
#Directive({
selector: '[clickOutside]'
})
export class ClickOutsideDirective {
constructor(private _elementRef : ElementRef) {
}
#Output()
public clickOutside = new EventEmitter();
#HostListener('document:click', ['$event.target'])
public onClick(targetElement) {
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this.clickOutside.emit(null);
}
}
}
The directive can then be used this way, after declaring it in the concerned module:
<!-- HTML Template -->
<ion-content (clickOutside)="handleOutsideClick()"><!-- ... --></ion-content>
<!-- Typescript code -->
handleOutsideClick() {
//Handle My outside Click
}
Yeah, It's been 7 months since asked.
Stucked with the same issue; this solved the issue
TS
#ViewChild('content') content: ElementRef
#HostListener('document:click', ['$event'])
andClickEvent(event) {
if (!this.content.nativeElement.contains(event.target)) {
if (!this.navCtrl.isTransitioning() && this.navCtrl.getActive()) {
this.close()
}
}
}
HTML
<ion-content #content>

Ionic 3 - How to prevent the user from clicking slide pager from slide 0 to slide 2 if slide 1 is not answered

I am working on a Ionic 3 questionnaire application where the user cannot go from one slide to another until the question is answered on a slide. I got everything working except pager (this dots) is allowing the user to go from one slide to another even though question# 2 is not answered.
I added the following logic but its not working as expected. Its still allowing me to jump from slide 0 to slide 2. Adding this.slideTo(this.currentIndex) changed the dot to be highlighted for slide 0 however its showing the contents of Slide# 2.
onSlideWillChange(event) {
let answerNotSelected: boolean = false;
for (let i: number = 0; i < this.slides.getActiveIndex(); i++) {
answerNotSelected = this.questionnaire.questions[i].selectedAnswer === undefined;
if (answerNotSelected) {
console.log('Returning from newQuestionIndex ' + this.slides.getActiveIndex() + ' to current slide:' + this.currentQuestionIndex);
this.slideTo(this.currentQuestionIndex);
}
} }
You could just lock the slides, and only enable back when the question has an answer. That way, the pager won't change the current slide (It would just be there to give the user some feedback about how many other questions are there in the questionnaire):
import { Component, ViewChild } from '#angular/core';
import { NavController, Content, Slides } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'app/home.page.html'
})
export class HomePage {
#ViewChild(Slides) slides: Slides;
ngOnInit() {
this.slides.lockSwipes(true); // Lock the slides
}
public showPrevious(): void {
this.slides.lockSwipes(false); // Unlock the slides
this.slides.slidePrev(500); // Move the the previous
this.slides.lockSwipes(true); // Lock the slides again
}
public showNext(): void {
this.slides.lockSwipes(false); // Unlock the slides
this.slides.slideNext(500); // Move the the next
this.slides.lockSwipes(true); // Lock the slides again
}
}
Please take a look at this working plunker for a demo.
I had the same problem - for anyone in the future I solved it in a albeit slightly hacky way (since _paginationContainer is an internal variable). You could probably also as easily just find elements in the DOM.
I called this on
ionSlideWillChange
, but it could probably be called on any of the swipe events.
ion-slide is very poorly documented, but it is based off of http://idangero.us/swiper/api/ and has all the same methods so you can look here for better documentation.
lockAll() {
var current = this.slider.getActiveIndex();
let array = this.slider._paginationContainer.childNodes
for (let index = 0; index < array.length; index++) {
var button : any = array[index];
if (this.inRange(index, current -1, current +1)) {
button.disabled = false;
}
else {
button.disabled = true
}
}
}
inRange(x, min, max) {
return ((x-min)*(x-max) <= 0);
}