I have a master - detail tab in ionic 4, the master tab gets the list of items that I've created in detail page, but when i create an item and i returned to the tab page, it doesn't refresh automatically.
I've tried using ionDidEnter, ionWillEnter, but nothing works.
This is my code in detail page:
async saveActividad() {
await loading.present().then(async () => {
await this.db.insertItem(item);
this.router.navigateByUrl('/tabs/tab1');
});
}
And this is my code in master page ("tab1")
ionViewWillEnter() {
this.db.getItems()
.then(
(data) => {
if (data !== '') {
this.list = data;
}
}
);
}
You have an TyPo in the event method call.
Instead of:
ionWillEnter() {
// todo
}
Try:
ionViewWillEnter() {
// todo
}
Ionic Page Events explanation
Name - Description
ionViewWillEnter - Fired when the component routing to is about to animate into view.
ionViewDidEnter - Fired when the component routing to has finished animating.
ionViewWillLeave - Fired when the component routing from is about to animate.
ionViewDidLeave - Fired when the component routing to has finished animating.
2) Or if only your tabs are just non-component based html, you could play with the *ngIf directive and invoke a method before returning true:
E.g.:
<section class="master-detail" *ngIf='showIfAvailable();'></section>
async showIfAvailable() {
this.list = await this.db.getItems();
if (list) {
return true;
}
}
Refresh data when function is invoked!
-Best Regards.
Related
I have a component that loads some HTML (converted from MD via marked library) and diplays it to the page and if a row is clicked, the document will scroll to the appropriate HTML element with the matching headerID on the page.
import { marked } from 'marked';
const [content, setContent] = React.useState('');
React.useEffect(() => {
if (!source) {
return;
}
getRequest(source)
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
return response.text().then(faq => {
// sets page to html
setContent(marked(faq));
const nearestHeader = document.getElementById(headerID);
if (nearestHeader) {
// if search result is clicked, jump to result's nearest header
nearestHeader.scrollIntoView(true);
setRowClicked(false);
}
});
})
.catch(e => Dialog.error('Failed to load page.', e));
}, [source, rowClicked]);
However, when I go to test this code the 'nearestHeader' object is always null even after I verified that the headerID matches up with the existing HTML element's ID I want to navigate to. How can I make sure document is ready/loaded before attemping the getElementById call without using extra libraries?
Solved by adding another useEffect call which waits on content of page to be set first. Removed nearestHeader code from the initial useEffect() call that sets content
React.useEffect(() => {
const nearestHeader = document.getElementById(headerID);
if (nearestHeader) {
nearestHeader.scrollIntoView(true);
setRowClicked(false);
}
}, [content]);
I am trying to display a routerlink name based on a condition. I want to display the div section routerLink name if condition is true.If i check {{isNameAvailable}}, first it displays false and after this.names got the values it shows true.Since in the component getDetails() method is asynchronous this.names getting the values after html template render.Therefore this routerLink does n't display.Therefore I want to display div section after some time. (That 's the solution i have) Don't know whether is there any other solution.
This is my html file code.
<main class="l-page-layout ps-l-page-layput custom-scroll bg-white">
{{isNameAvailable}}
<div class="ps-page-title-head" >
<a *ngIf ="isNameAvailable === true" [routerLink]="['/overview']">{{Name}}
</a>
{{Name}}
</div>
</main>
This is my component.ts file
names= [];
isNameAvailable = false;
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.subscribe(params => {
this.names.push(params.Names);
console.log(this.names);
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
});
});
}
resolveAfterSeconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 900);
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
async getValues() {
await this.resolveAfterSeconds(900);
}
And console.log(this.isLevelAvailable); also true. What I can do for this?
1.You do not have anything to show in the HTML only the isNameAvailable, because you do not have any assignment in the Name variable.
2.It is better to use the angular build-in async pipe,
when you want to show the returned value from observables.
3.When you are using the *ngIf directive you can skip *ngIf ="isNameAvailable === true" check because the variable is boolean type, you gust write *ngIf ="isNameAvailable", it will check also for null but NOT for undefined
It is working because the *ngIf directive is responsible for checking and rendering the UI, you can see how many times the directive is checking by calling an function and print and answer in the console.
By any chance do you have changeDetection: ChangeDetectionStrategy.OnPush docs set in component annotation? That might explain this behaviour. With it Angular run change detection only on component #Input()'s changes and since in your case there were non it did not run change detection which is why template was not updated. You could comment that line to check if that was cause of the issue. You are always able to run change detection manually via ChangeDetectorRef.detectChange() docs which should solve you problem
constructor(private cd: ChangeDetectorRef) {}
...
getDetails() {
this.route.params.subscribe(params => {
...
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
this.cd.detectChanges(); // solution
console.log(this.isNameAvailable);
});
});
}
This stackblitz show this bug and solution. You can read more about change detection here
You could use RxJS timer function with switchMap operator instead of a Promise to trigger something after a specific time.
Try the following
import { Subject, timer } from 'rxjs';
import { takeUntil, switchMap } from 'rxjs/operators';
names= [];
isNameAvailable = false;
closed$ = new Subject();
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.pipe(
switchMap((params: any) => {
this.names.push(params.Names);
return timer(900); // <-- emit once after 900ms and complete
}),
takeUntil(this.closed$) // <-- close subscription when `closed$` emits
).subscribe({
next: _ => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
}
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
ngOnDestroy() {
this.closed$.next(); // <-- close open subscriptions when component is closed
}
I want to show a loader on pop-up/model once a HTTP request triggers and hide it when all http requests are completed.
I am new to angular and I am not able to find a way to implement it.
My app.html:
<ng4-loading-spinner [threshold]="2000" [template]="template" [loadingText]="'Please wait...'" [zIndex]="9999"> </ng4-loading-spinner>
app.ts
this.spinnerService.show();
I am using import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
It depends on the scope. If the request is inside of a component or will be called in a service instance of a component, then you can set an *ngIf with a flag variable on true on the loading spinner before it starts and after the response arrives you set the variable to false:
HTML
<ng4-loading-spinner *ngIf="isLoading"></ng4-loading-spinner>
TS:
getData() {
this.isLoading = true;
this.httpClient.get(...).subscribe(
() => {
this.isLoading = false;
}
);
}
When using a generic modal or toast with a confirm button, it becomes useful to be able to pass an action into this component so it can be dispatched when you click confirm.
The action may look something like this:
export function showConfirm({modalConfirm}) {
return {
type: 'MODALS/SHOW_MODAL',
payload: {
modalId: getUuid(),
modalType: 'CONFIRM',
modalConfirm : modalConfirm,
},
};
}
Where modalConfirm is another action object such as:
const modalConfirm = {
type: 'MAKE_SOME_CHANGES_AFTER_CONFIRM',
payload: {}
}
The modalConfirm action is dispatched inside the modal component using dispatch(modalConfirm) or even dispatch(Object.assign({}, modalConfirm, someResultFromTheModal)
Unfortunatley this solution only works if modalConfirm is a simple redux action object. This system is clearly very limited. Is there anyway you can pass a function (such as a thunk) in instead of a simple object?
Ideally, something full featured likes this:
const modalConfirm = (someResultFromTheModal) => {
return (dispatch, getState){
dispatch({
type: 'MAKE_SOME_UPDATES',
payload: someResultFromTheModal
})
dispatch({
type: 'SAVE_SOME_STUFF',
payload: http({
method: 'POST',
url: 'api/v1/save',
data: getState().stuffToSave
})
})
}
}
Funny, putting an action object in the store and passing it as a prop to a generic dialog is exactly the approach I came up with myself. I've actually got a blog post waiting to be published describing that idea.
The answer to your question is "Yes, but....". Per the Redux FAQ at http://redux.js.org/docs/FAQ.html#organizing-state-non-serializable , it's entirely possible to put non-serializable values such as functions into your actions and the store. However, that generally causes time-travel debugging to not work as expected. If that's not a concern for you, then go right ahead.
Another option would be to break your modal confirmation into two parts. Have the initial modal confirmation still be a plain action object, but use a middleware to watch for that being dispatched, and do the additional work from there. This is a good use case for Redux-Saga.
I ended up using string aliases to an actions library that centrally registers the actions.
Modal emmiter action contains an object with functionAlias and functionInputs
export function confirmDeleteProject({projectId}) {
return ModalActions.showConfirm({
message: 'Deleting a project it permanent. You will not be able to undo this.',
modalConfirm: {
functionAlias: 'ProjectActions.deleteProject',
functionInputs: { projectId }
}
})
}
Where 'ProjectActions.deleteProject' is the alias for any type of complicated action such as:
export function deleteProject({projectId}) {
return (dispatch)=>{
dispatch({
type: 'PROJECTS/DELETE_PROJECT',
payload: http({
method: 'DELETE',
url: `http://localhost:3000/api/v1/projects/${projectId}`,
}).then((response)=>{
dispatch(push(`/`))
}),
meta: {
projectId
}
});
}
}
The functions are registered in a library module as follows:
import * as ProjectActions from '../../actions/projects.js';
const library = {
ProjectActions: ProjectActions,
}
export const addModule = (moduleName, functions) => {
library[moduleName] = functions
}
export const getFunction = (path) => {
const [moduleName, functionName] = path.split('.');
// We are getting the module only
if(!functionName){
if(library[moduleName]){
return library[moduleName]
}
else{
console.error(`Module: ${moduleName} could not be found.`);
}
}
// We are getting a function
else{
if(library[moduleName] && library[moduleName][functionName]){
return library[moduleName][functionName]
}
else{
console.error(`Function: ${moduleName}.${functionName} could not be found.`);
}
}
}
The modalConfirm object is passed in to the modal by props. The modal component requires the getFunction function in the module above. The modalConfirm object is transformed into a function as follows:
const modalConfirmFunction = (extendObject, modalConfirm) => {
const functionFromAlias = getFunction(modalConfirm.functionAlias);
if(functionFromAlias){
dispatch(functionFromAlias(Object.assign({}, modalConfirm.functionInputs, extendObject)));
}
}
As you can see, this function can take in inputs from the modal. It can execute any type of complicated action or thunk. This system does not break time-travel but the centralized library is a bit of a drawback.
I'm working with localStorage and I want to change a counter when closing the page.
I found that onbeforeunload is the event which can be useful, so I decided to call my function in it. (onunload doesn't work for me in firefox/chrome)
But when I try to call a function , it doesn't do anything.
Is there any way to call a function when closing/refreshing a tab ?
class MyLib{
constructor(){
window.onbeforeunload = function() {
this._onClosePage();
};
}
_onClosePage() {
let openWindowsCount = this._getItem('countOpenWindow');
openWindowsCount--;
if (openWindowsCount === 0) {
this._clearUp();
}
}
_clearUp() {
this._removeItem('countPagesVisited');
}
}
UPDATE
As suggested by Oday , I fixed the binding. But now , it works randomly.
In chrome, it doesn't catch the refresh event, but sometimes it catches the exit.
In firefox, it catches the exit, and randomly catches the refresh.
class MyLib{
constructor(){
document.getElementsByTagName('body')[0].onbeforeunload = this._onClosePage.bind(this);
}
_onClosePage() {
let openWindowsCount = this._getItem('countOpenWindow');
openWindowsCount--;
if (openWindowsCount === 0) {
this._clearUp();
}
}
_onClosePage() { // call it once the page is closed or refreshed
let openWindowsCount = localStorage.getItem('countOpenWindow');
openWindowsCount--;
localStorage.setItem('countOpenWindow' , openWindowsCount);
if (openWindowsCount === 0) {
this._clearUp();
}
}
_clearUp() {
localStorage.removeItem('countOpenWindow');
}
}
In order to clean the localStorage on beforeunload, instead of using window.onbeforeunload directly, you should use the window.addEventListener() method to start listening to beforeunload event. This also allows you to remove the event listener when you find fit.
See this explanation on Mozilla Developers Documentation:
Binding to this event can be used to prevent the browser from fully
caching the page in cases where content is rendered by javascript. In
certain circumstances when returning to a page that has executed
javascript in order to populate content, you may find the javascript
not running upon the return visit when navigating back. If
window.onbeforeunload has been bound (and thus triggered when leaving
that page) javascript in the page will be triggered on the subsequent
return visit and therefore update the content.
Full text here: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
This is probably the reason for the randomness you mentioned.
As an example, see above React component that adds the event listener when it finishes mounting, and than, when it is unmounted it removes the event listener. It is a very elementary example, but I tested it on Firefox 60, Chrome 69 canary, Safari 11.1 and it worked properly.
import React, { Component } from 'react'
class App extends Component {
constructor(props) {
super(props)
this.handleLoad = this.handleLoad.bind(this)
this.handleBeforeunload = this.handleBeforeunload.bind(this)
}
componentDidMount() {
this.handleLoad()
window.addEventListener('beforeunload', this.handleBeforeunload)
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.handleBeforeunload)
}
render() {
return (
<div className="App">
<p>
Hello World!
</p>
</div>
)
}
handleLoad() {
let countOpenWindow = parseInt(localStorage.getItem('countOpenWindow'), 10)
if (!countOpenWindow) {
countOpenWindow = 0
}
localStorage.setItem('countOpenWindow', ++countOpenWindow)
}
handleBeforeunload() {
let countOpenWindow = parseInt(localStorage.getItem('countOpenWindow'), 10)
if (countOpenWindow > 1) {
localStorage.setItem('countOpenWindow', --countOpenWindow)
} else {
localStorage.removeItem('countOpenWindow')
}
}
}
export default App
You need to capture the context of your class inside the onbeforeunload event handler. Currently, the 'this' refers to the window which fires the event.
constructor(){
let that = this;
window.onbeforeunload = function() {
that._onClosePage();
};
}