I have a paginated API that looks like http://127.0.0.1:8000/api/articles/?page=2 and I want to use the same pagination on the front end in Aurelia.
I created two buttons for the next and previous pages:
<button
type="button"
click.delegate="getArticles(articles.previous)">prev</button>
<button
type="button"
click.delegate="getArticles(articles.next)">next</button>
When I click next there is a new GET request and the articles list get updated but I also want to add the parameters on the URL so the user can see he's on the second page.
So, how can I add /?page=2 to the end of the route.
I know how to add parameters using a different component as the child but this time I'm using the same component.
Thank you.
Aurelia supports query parameters in routes out of the box. You don't need to do define page parameter in the route itself. All query parameters are just given to the params parameter of activate method.
import { inject } from 'aurelia-framework';
import { Router, activationStrategy } from 'aurelia-router';
#inject(Router)
export class Articles(Router) {
constructor(router) {
this.router = router;
}
activate(params) {
this.page = parseInt(params.page || '1');
// TODO: Load your articles from the API here
// this.articles = <fetch call using this.page>
}
// This is necessary to tell Aurelia router not to reuse
// the same view model whenever navigating between pages
// so that the activate method gets called each time
determineActivationStrategy() {
return activationStrategy.replace;
}
// TODO: Check if we can go to previous and next page, etc.
nextPage() {
this.router.navigateToRoute('articles', { page: this.page + 1 });
}
previousPage() {
this.router.navigateToRoute('articles', { page: this.page - 1 });
}
}
Related
I display the header and footer of a website with the help of vuex + vue-router + dynamic components.
I fetch the current routes from vue-router via vuex and "inject" the data into App.vue, where I have my header and footer. I can console.log the correct routes of every page.
The app.vue markup looks like that:
<component :is="selectedHeader"></component>
<router-view></router-view>
<component :is="selectedFooter"></component>
Now I use the computed properties selectedFooter and selectedHeader in order to display the correct components:
computed: {
// eslint-disable-next-line vue/return-in-computed-property
selectedHeader() {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.currentRoute = this.$store.getters.getRoute
if(this.currentRoute) {
if(this.currentRoute.includes("de")) {
return "de-header";
}
if(this.currentRoute.includes("en")) {
return "en-header";
}
if(this.currentRoute.includes("es")) {
return "es-header";
}
}},
// eslint-disable-next-line vue/return-in-computed-property
selectedFooter() {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.currentRoute = this.$store.getters.getRoute
if(this.currentRoute) {
if(this.currentRoute.includes("de")) {
return "de-footer";
}
if(this.currentRoute.includes("en")) {
return "en-footer";
}
else if(this.currentRoute.includes("es")) {
return "es-footer";
}
}},
It pricipally works, but I think that I'm already experiencing side effects because I don't use the computed properties correctly, e.g. one route displays to the wrong language header, even though the route data from the store is definitely correct?
I guess that it would be better to use watchers, but how?
So I have implemented Echarts with a Vue application, on one of the charts, I am trying to get the item clicked and pass it back to the parent component that way I can do specific calculations to it.
The 'on click' method works and I can console.log('params') easily, however, trying to reach any other functions outside of it is not possible for some reason...
here is my code...
data() {
return {
myChart: null,
selectedState: {}
}
}.
mounted() {
this.myChart = echarts.init(document.getElementById("geoMap"))
this.myChart.on('click', function(params){
// It will run the console.log with correct info, but the
// method is not reachable...
console.log(params)
this.setSelectedState(params)
})
},
// Inside my vue script this is just a method to set the data for now...
methods: {
setSelectedState(params){
this.selectedState = params
},
}
any help would be nice!! thanks!
You're not in the Vue component context when listening to the chart event, so you have to change your callback function to an arrow one to access the component's this :
this.myChart.on('click', params => {
this.setSelectedState(params)
});
methods: {
setSelectedState(params) {
console.log(params);
this.selectedState = params
}
}
By the way, you should use ref instead of getting your div with document.getElementById to attach your chart :
<div ref="geoMap"></div>
this.myChart = echarts.init(this.$refs.geoMap);
I've got a grid component that I use in many routes in my app. I'd like to persist its state (ie. paging, search param) and restore it when the user comes back to the grid (ie. from editing a row). On the other hand, when the user starts a new flow (ie. by clicking a link) then the page is set to zero and web service is called with the default param.
How can I recognise the user does come back rather then starts a new flow?
When I was researching the problem I've come across the following solutions.
Unfortunatelly they didn't serve me
1/ using router scroll behaviour
scrollBehavior(to, from, savedPosition) {
to.meta.comeBack = savedPosition !== null;
}
It does tell me if the user comes back. Unfortunately the scroll behaviour runs after grid's created and mounted hooks are called. This way I have no place to put my code to restore the state.
2/ using url param
The grid's route would have an optional param. When the param is null then the code would know it's a new flow and set a new one using $router.replace routine. Then the user would go to editing, come back and the code would know they come back because the route param != null. The problem is that calling $router.replace re-creates the component (ie. calling hooks etc.). Additionally the optional param mixes up and confuses vue-router with other optional params in the route.
HISTORY COMPONENT
// component ...
// can error and only serves the purpose of an idea
data() {
return {
history: []
}
},
watch: {
fullRoute: function(){
this.history.push(this.fullRoute);
this.$emit('visited', this.visited);
}
},
computed: {
fullRoute: function(){
return this.$route.fullPath
},
visited: function() {
return this.history.slice(-1).includes(this.fullRoute)
}
}
the data way
save the information in the browser
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return JSON.parse(local.storage.gridData)
},
set: function(dataObj){
local.storage.gridData = JSON.stringify(dataObj)
}
}
}
//...
use statemanagement
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return this.$store.state.gridData || {}
},
set: function(dataObj){
this.$store.dispatch("saveGrid", gridData)
}
}
}
//...
use globals
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return window.gridData || {}
},
set: function(dataObj){
window.gridData = dataObj
}
}
}
In my Angular 5 application, the user may navigate to a route which uses the same route, but with different parameters. For example, they may navigate from /page/1 to /page/2.
I want this navigation to trigger the routing animation, but it doesn't. How can I cause a router animation to happen between these two routes?
(I already understand that unlike most route changes, this navigation does not destroy and create a new PageComponent. It doesn't matter to me whether or not the solution changes this behavior.)
Here's a minimal app that reproduces my issue.
This is an old question but that's it if you're still searching.
Add this code to your app.Component.ts file.
import { Router, NavigationEnd } from '#angular/router';
constructor(private _Router: Router) { }
ngOnInit() {
this._Router.routeReuseStrategy.shouldReuseRoute = function(){
return false;
};
this._Router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
this._Router.navigated = false;
window.scrollTo(0, 0);
}
});
}
By using this code the page is going to refresh if you clicked on the same route no matter what is the parameter you added to the route.
I hope that helps.
Update
As angular 6 is released with core updates you don't need this punch of code anymore just add the following parameter to your routs import.
onSameUrlNavigation: 'reload'
This option value set to 'ignore' by default.
Example
#NgModule({
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload'})],
exports: [RouterModule]
})
Stay up to date and happy coding.
I ended up creating a custom RouteReuseStrategy which got the job done. It's heavily based on this answer.
export class CustomReuseStrategy implements RouteReuseStrategy {
storedRouteHandles = new Map<string, DetachedRouteHandle>();
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return false;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
this.storedRouteHandles.set(route.routeConfig.path, handle);
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return false;
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this.storedRouteHandles.get(route.routeConfig.path);
}
// This is the important part! We reuse the route if
// the route *and its params* are the same.
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig &&
future.params.page === curr.params.page;
}
}
Check it out on StackBlitz!
I'm using beta.0 because this outstanding bug prevents angular 2 from working in IE in beta.1 and beta.2.
Relevant code from SearchBar.ts
#Component({
selector : 'search-bar',
templateUrl: 'views/searchbar.html'
})
export class SearchBar {
private history: SearchHistoryEntry[] = [];
#Output() onHistory = new EventEmitter();
constructor() {
this.history = JSON.parse(localStorage.getItem('SearchHistory')) || [];
}
ngOnInit() {
// The constructor doesn't have #Outputs initialized yet. Emit from this
// life cycle hook instead to be sure they're received on the other side
debugger;
this.onHistory.emit(this.history);
}
}
Relevant code from home.html
<search-bar (onHistory)="SearchBarHistory($event)"></search-bar>
Relevant code from home.ts
SearchBarHistory(history: SearchHistoryEntry[]) {
debugger;
this.history = history;
}
In Chrome this works just fine. The SearchBar's constructor correctly reads from localStorage, in ngOnInit it emits to my Home component who receives it, it's stored locally and the UI bindings tied to history update to show the information as it all should.
In IE 11 this does not work. ngOnInit won't run until I click inside my search bar. It seems that any #Input or lifecycle hook (specifically I've tested ngOnInit, ngAfterContentInit, and ngAfterViewInit and they all behave the same) doesn't run until the component's change detection is triggered. If I refresh the page then it runs exactly like Chrome where no interaction is required for #Inputs or lifecycle hooks to be called and my history goes through and gets bound like it should.
I think this is a bug of the beta but in the mean time is there anything I can do to make it work the first time without an interaction or page refresh?
I am having the same issue I tried it to resolve by forcing detectChanges like:
import {Injectable,ApplicationRef, NgZone} from '#angular/core';
#Injectable()
export class IeHackService {
constructor(
private _appRef: ApplicationRef,
private _zone: NgZone) {}
private isIe() {
let ua = window.navigator.userAgent;
let msie = ua.indexOf('MSIE ');
if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) // If Internet Explorer, return version number
return true;
return false;
}
onComponentLoadInIe() {
if (this.isIe()) {
this._zone.run(() => setTimeout(() => this._appRef.tick(), 5));
}
}
}
Then in Every Route component that uses Lifecycle Hooks I called
constructor(private dialogService: ModalDialogService,ieHackService: IeHackService) {
ieHackService.onComponentLoadInIe();
}
I had this issue as well, I used a workaround to automatically refresh the page if the bug occurs, hoping that the bug will eventually be solved.
It's very ugly, but for now it works at least.
declare var Modernizr: any;
#Component({
selector : 'search-bar',
templateUrl: 'views/searchbar.html'
})
export class SearchBar {
private history: SearchHistoryEntry[] = [];
#Output() onHistory = new EventEmitter();
ie11hack: boolean = true;
constructor() {
this.history = JSON.parse(localStorage.getItem('SearchHistory')) || [];
if (!Modernizr.es6collections || navigator.userAgent.toLowerCase().indexOf("safari") !== -1) {
setTimeout(() => {
if (this.ie11hack) {
window.location.reload();
}
}, 500);
}
}
ngOnInit() {
ie11hack = false;
// The constructor doesn't have #Outputs initialized yet. Emit from this
// life cycle hook instead to be sure they're received on the other side
debugger;
this.onHistory.emit(this.history);
}
}
Edit, a less ugly partial fix:
The issues is (I think) caused by using a direct url rather then using the angular router (javascript).
If you want your website to work if people enter their url manually then you still need the above hack, but otherwise you may do what I did below (you may want to do that anyway).
I changed this:
<!-- html -->
<a href="#/objects/objectthingy/{{myObject.id}}" class="my-object-class">
To this:
<!-- html -->
<a href="javascript:void(0)" (click)="openMyObject(myObject.id)" class="my-object-class">
// typescript
openMyObject(objectId: number) {
this.router.navigate(['/Objects', 'ObjectThingy', { id: objectId}]);
}
and ngAfterViewInit method was called again.