I am calling a data service in an angular 2 component to load data on the ngOnInit() function. This component is placed on a Ionic tabs page. The ngOnInit() function is only called on initialization, but not on every navigation to the tab. I want to reload data from the data service on each navigation to the page, to refresh the component with the latest data.
How can I call a function in the component on each navigation to a tabs page?
This is my component:
#Component({
selector: 'reservation-list',
templateUrl: 'build/components/reservation-list.component.html',
bindings: [DataService, TimeService]
})
export class ReservationListComponent {
public items: any = [];
constructor(private dataService: DataService) { }
public ngOnInit() {
// this I want to call on each tab navigation!
this.items = this.dataService.getEvents();
}
}
My tabs are basically the ionic2-tabs example:
#Page({
templateUrl: 'build/pages/tabs/tabs.html'
})
export class TabsPage {
// this tells the tabs component which Pages
// should be each tab's root Page
tab1Root: any = Page1;
tab2Root: any = Page2;
tab3Root: any = Page3;
}
And the page is a basic ionic page where the component is insert:
<reservation-list></reservation-list>
#Page({
templateUrl: 'build/pages/page1/page1.html',
directives: [ReservationListComponent]
})
export class Page1 {
constructor() {
}
}
I think you can add a click event handler when you click your tabs and call that function.
In your tag
<a (click)="getEvents()"></a>
In you Component
getEvents() {
this.items = this.dataService.getEvents();
}
Please follow the life cycle of ionic and use below method inside tab child pages.
ionViewWillEnter()
{
//apply your code
}
This method will call always when you come on page.
Related
I have a component which needs to show the data in the grid on the component/page Load and when a button is clicked from parent component it needs refresh the grid with new data. My component is like below
export class TjlShipdateFilterComponent implements DoCheck {
tljShipDate: ShipDateFilterModel[];
constructor(private psService: ProjectShipmentService) {
}
ngDoCheck() {
// this data is from the service, trying to get it on Page load
}
#Input() filter: ShipDateFilterModel[];
//Load or refresh the data from parent when the button clicked from parent component
ngOnChanges(changes: SimpleChanges) {
}
The ngOnChanges works fine, it gets the data from the parent component and displays when the button is clicked from the parent component. But on load of the page/component the grid it doesn't show anything and says this.psService.tDate; is undefined.
Below is the service where I get the tDate
export class ProjectShipmentService {
......
constructor(service: DataService, private activatedRoute: ActivatedRoute) {
service.get<ShipDateFilterModel[]>(this.entityUrl).subscribe(x => this.tDate = x);
}
I am unsure what am I missing here. How can I achieve this scenario
It happened because when the component is loaded, the request in your service may not completed and the data may not return yet, that why tDate is undefined, try subscribe to it inside your component, also use ngOnInit() instead of ngDoCheck().
In your service:
tDate: Observable<ShipDateFilterModel[]>
constructor(service: DataService, private activatedRoute: ActivatedRoute) {
...
this.tDate = service.get<ShipDateFilterModel[]>(this.entityUrl)
}
In your component:
export class TjlShipdateFilterComponent implements OnInit, OnChanges {
tljShipDate: ShipDateFilterModel[];
constructor(private psService: ProjectShipmentService) {
}
ngOnInit() {
// this data is from the service, trying to get it on Page load
this.psService.tDate.subsribe(x => this.tljShipDate = x);
}
#Input() filter: ShipDateFilterModel[];
//Load or refresh the data from parent when the button clicked from parent component
ngOnChanges(changes: SimpleChanges) {
if (changes.filter && changes.filter.currentValue)
{
this.tljShipDate = this.filter;
}
}
}
You have a couple options here.
NgOnInit will run when the component is created, before it is rendered. This is the most common way to load data on component initialization.
If you need the data even before the component is initialized, then you may need to utilize a Resolver.
Here's an example:
import { Injectable } from '#angular/core'
import { HttpService } from 'services/http.service'
import { Resolve } from '#angular/router'
import { ActivatedRouteSnapshot } from '#angular/router'
#Injectable()
export class DataResolver implements Resolve<any> {
constructor(private http: HttpService) { }
resolve(route: ActivatedRouteSnapshot) {
return this.http.getData(route.params.id);
}
}
Then, in your route config:
{
path: 'data/:id',
component: DataComponent,
resolve: { data: DataResolver }
}
The inclusion of the ActivatedRouteSnapshot is optional, you only need it if you're using route data, like params.
Edit:
Looking at your example closer, is it possible that the ngDoCheck is firing before the psService subscription does?
I'd like to be able to access the SearchResults component, (when it has been clicked), in the root component (AppComponent) as I'm looking to set different properties on the SearchResults component such as;
I'd like to set an attribute on the SearchResults component so that it shows the "close" text
Also, I'd to set the click event on the SearchResults to redirect elsewhere or actually enable it as a multi-select so that it stays selected until a user proceeds to the next step for example.
I'm trying to make the SearchResults and SearchResult components as re-usable as possible so we're able to state in the parent component which would include the <app-searchresults> selector what action we'd like our SearchResults components to actually be when they are clicked.
The only way I can really see doing this is using EventEmitter to pass the event up once through the SearchResult component then onto the parent component and then a Service to hold selected values but I'm still stuck around enabling the SearchResults component as either a component which redirects when clicked or stays selected? Is this actually possible or do I need to create a different SearchResults component for each different state I'd like?!
export class AppComponent {
#ViewChildren(SearchresultComponent) components: QueryList<SearchresultComponent>;
name = 'Angular';
ngAfterViewInit() {
this.components.changes.subscribe((r) => { console.log(r) });
}
}
SearchResults.ts
#Component({
selector: 'app-searchresults',
templateUrl: './searchresults.component.html',
styleUrls: ['./searchresults.component.css']
})
export class SearchresultsComponent implements OnInit {
#ViewChildren(SearchresultComponent) components: QueryList<SearchresultComponent>;
constructor() { }
ngOnInit() {
}
}
SearchResults.html
<h1>Search Results<h1>
<app-searchresult result ="first"></app-searchresult>
<app-searchresult result ="second"></app-searchresult>
<app-searchresult result ="third"></app-searchresult>
SearchResult.ts
#Component({
selector: 'app-searchresult',
templateUrl: './searchresult.component.html',
styleUrls: ['./searchresult.component.css']
})
export class SearchresultComponent implements OnInit {
#Input()
result: string;
isSelected: boolean;
constructor() { }
ngOnInit() {
}
toggleClickedState(){
if(!this.isSelected){
this.isSelected = !this.isSelected;
}
}
}
SearchResult.html
<div>
<p (click)=toggleClickedState() [ngClass]="isSelected? 'selected' : '' "> Search Result : {{result}}</p>
<p *ngIf="isSelected" class="cross" (click)="isSelected = false;">close</p>
<div>
I've included a link to structure of an app that references the above;
https://stackblitz.com/edit/angular-cjhovx
This is my footer component. I am calling few pages from the footer bar as follows. When i click on the footer, it is loading the stale component. i debugged it and found out it is not calling the constructor of the component (where i have the loading logic to refresh the data). i have to click twice from the footer to call the component to get the correct data
tab-footer.component.html
<ion-tabs>
<ion-tab [root]="home" tabIcon="md-home"
[rootParams]="homeparams"></ion-tab>
<ion-tab [root]="alertsPage" tabIcon="md-notifications-outline">
</iontab>
<ion-tab [root]="flagged" tabIcon="flag"></ion-tab>
</ion-tabs>
#Component({
selector: 'app-tab-footer',
templateUrl: './tab-footer.component.html',
styleUrls: ['./tab-footer.component.scss']
})
export class TabFooterComponent {
home = HomeComponent;
alertsPage = AlertsComponent;
flagged = FlaggedComponent;
homeparams: any;
constructor(public navparam: NavParams) {
this.homeparams = navparam;
}
}
Here is my one of the components that is called from footer
#Component({
selector: 'app-flagged',
templateUrl: './flagged.component.html',
styleUrls: ['./flagged.component.scss']
})
export class FlaggedComponent {
someData1: any;
someData:any[] = [];
someObj = {}
constructor(public someService: SomeServiceService,
public exampleService: ExampleService,
public navParams: NavParams,
public navCtrl: NavController) {
// some loading logic
}
Can anyone help finding out why it is not calling constructor every time?
Do I have specify some property to load it fresh?
Thank you
Add the lifecycle event ionViewDidEnter or ionViewWillEnter to your FlaggedComponent class and move your update or loading logic from the constructor into one of those methods.
ionViewDidEnter() {
console.log('the page has entered');
}
ionViewWillEnter() {
console.log('the page is about to enter');
}
ionViewDidEnter is called when the page has fully entered and is now the active page.
This event will fire, whether it was the first load or a cached page.
ionViewWillEnter is called when the page is about to enter and become the active page.
reference: https://ionicframework.com/docs/api/navigation/NavController/#lifecycle-events
Is this possible to create modal with contains Tabs and when select item pass data to parent view?
I known that Tabs have own, separate history stack so if is not possible what is best way to implement that like Tabs-looking way?
Creating modal with tabs from parent view:
selectContractor() {
let contractorsModal = this.modalCtrl.create('VisitAddTabsPage', {routeId: this.routeId});
contractorsModal.present();
contractorsModal.onDidDismiss(data => {
console.log(data);
})
}
VisitAddTabsPage.ts
#Component({
selector: 'page-visit-add-tabs',
templateUrl: 'visit-add-tabs.html'
})
#IonicPage()
export class VisitAddTabsPage {
ownContractorsRoot = 'OwnContractorsPage'
closestContractorsRoot = 'ClosestContractorsPage'
allContractorsRoot = 'AllContractorsPage'
constructor(public navCtrl: NavController,
public navParams: NavParams,
public viewCtrl: ViewController) {
}
}
visit-add-tabs.html
<ion-tabs tabsPlacement="top" selectedIndex="1">
<ion-tab [root]="ownContractorsRoot" tabTitle="Moi" tabUrlPath="own" tabIcon="star"></ion-tab>
<ion-tab [root]="closestContractorsRoot" tabTitle="W pobliżu" tabUrlPath="closest" tabIcon="locate"></ion-tab>
<ion-tab [root]="allContractorsRoot" tabTitle="Wszyscy" tabUrlPath="all" tabIcon="contacts"></ion-tab>
</ion-tabs>
Closest-contractors tab (closest-contractors.ts)
handleSelectedContractor(data) {
console.log(this.navCtrl);
console.log(this.appCtrl.getRootNav());
//this.viewCtrl.dismiss(data); <-- HOW TO DISMISS MODAL (and TABS) AND PASS DATA
}
Yes, you can do that using Events.
You can always emit an event, carrying any data you want, from any DOM element to any of it's parent dom elements.
So from the child page do something like this:
import { EventEmitter, Output } from '#angular/core';
#Component({
...
})
export class ChildPage {
#Output() tabGotClosed = new EventEmitter();
let data: any;
...
notifyPickupConfirmed() {
this.tabGotClosed.emit(data);
}
}
Then in the parent page's template you can just catch the event like you would with any other:
<ion-content (tabGotClosed)="onTabGotClosed($event)">
In this way, the page that opened the modal can finally have the data available to it after the modal closes by just looking into the data carried by the event event.detail.tabGotClosed.
i'm developing my first app in angular 4.0.
My problem is pretty simple but i would like to know if there is a best practice to solve my issue.
I have an header with position: 'fixed' and when the user scrolls the page this element changes some of its properties (height, background-size..) by adding a 'small' class dynamically.
This is my component
import {Component, OnInit, HostListener} from '#angular/core';
#Component({
selector: 'my-header',
templateUrl: './my-header.component.html',
styleUrls: ['./my-header.component.css']
})
export class HeaderComponent implements OnInit {
scrollState: boolean;
constructor() { }
ngOnInit() {
this.scrollState = false;
}
#HostListener('window:scroll', [])
toggleScrollState() {
if(window.pageYOffset == 0){
this.scrollState = false;
}
else{
this.scrollState = true;
}
}
}
and this the html
<header class="bk-blue clearfix" [ngClass]="{small: scrollState}">
<a class="sx" href="#">Login</a>
<a class="dx arrow-down"></a>
<a class="dx" href="#">It</a>
</header>
Everything works fine but this should happen only in the home page. In the other page the header element should already be in the 'small' state without any DOM manipulations based on the scroll event.
I was thinking of checking the current route to set an additional variable (false if the current route matches the home page path, true otherwise) and put that in OR with scrollState. Something like this:
<header class="bk-blue clearfix" [ngClass]="{small: notHome || scrollState}">
By doing so, however, i can't avoid calling the listener with its implications in term of reduced performance.
What is for you the best approach to avoid calling the listener even in internal pages where it is not necessary?
well, I would do this using ActivatedRouteSnapshot
#Component({...})
class Header implements OnInit {
readonly snapshot: ActivatedRouteSnapshot = null;
constructor(private route: ActivatedRoute) {
this.snapshot = route.snapshot;
}
ngOnInit() {
// if this.snapshot is HOMEPAGE
// then
// subscribe to the scroll and switch class when you need.
}
}
You could also set a property on your route.data which tells you to animate or not the header.
const routes: Routes = [
{
path: '',
component: HomeRouteComponent,
data: { ANIMATE_HEADER: true }
}
];
// header.ts
ngOnInit() {
if(this.snapshot.data.ANIMATE_HEADER) {
// do stuff here
}
}