Same news-detail clicking on different news on Ionic 4 (Newsapi) - ionic4

I'm following a tutorial here: https://www.youtube.com/watch?v=NJ9C7iY9350. Cannot understand why i'm doing click on a news and the news-detail is always the same, i'm checking my code and its the same as in the github. But don't know why cannot see the news-detail of every news, just remains the first news i do the click.
Attach my code:
NEWS
TS
goToNews(article) {
this.newsService.currentArticle = article;
console.log(article)
this.router.navigate(['/tabs/news-detail']);
}
getNews() {
this.newsService.getData(`top-headlines?countries=${this.selectedCountry}&category=technology`)
.subscribe(data => {
this.news = data;
console.log(this.selectedCountry)
})
}
HTML
<ion-list *ngIf="!isResult">
<ion-item *ngFor="let country of countries" (click)="selectCountry(country)">
{{ country }}
</ion-item>
</ion-list>
<ion-card *ngFor="let article of news?.articles" (click)="goToNews(article)">
<ion-card-content>
<ion-card-title>{{ article.title }}</ion-card-title>
</ion-card-content>
</ion-card>
NEWS-DETAILS
TS
export class NewsDetailPage implements OnInit {
article
constructor(private newsService: NewsService) { }
ngOnInit() {
this.article = this.newsService.currentArticle;
console.log(this.article)
}
}
tabs.router.module.ts
....
{ path: 'news', loadChildren:'../news/news.module#NewsPageModule' },
{ path: 'news-detail', loadChildren:'../news-detail/news-detail.module#NewsDetailPageModule' },
....

Finally i resolved it:
ionViewWillEnter() {
this.article = this.newsService.currentArticle;
console.log(this.newsService.currentArticle)
}
Doing this it always change everytime as i do click on a new "news"

Related

Vue 3 : issue in getting onchange event from child component

I made a component to provide a select including a button that reset the select to the initial state (no option selected).
I get well the onchange event in parent when an option is selected but nothing when the reset button is clicked, although the select is reset. In my use case the list is still filtered even when nothing is selected
Here is the parent file :
<template>
<liste-filter
:nom="'marque'"
:label="'Choose a make'"
:liste="marques"
v-model="selMarque"
#change="changefilter"></liste-filter>
<h1/>
<div v-for="vh in vehicules" :key="vh.lib" class="w-full">
{{ vh.lib }}
</div>
<div v-if="vehicules.length === 0">The list is empty</div>
</template>
<script>
import ListeFilter from "./ListeFilter.vue"
export default {
components: {
ListeFilter
},
data() {
return {
marques:[{id:"Renault",libelle:"Renault"},{id:"Peugeot",libelle:"Peugeot"}],
selMarque: "",
vehicules: [],
tabData:[{"lib":"Renault","modeles":[{"lib":"Clio"},{"lib":"Captur"}]},{"lib":"Peugeot","modeles":[{"lib":"208"},{"lib":"308"},{"lib":"3008"}]}]
};
},
methods: {
changefilter() {
this.vehicules = [];
for (var i = 0; i < this.tabData.length; i++) {
if(this.selMarque != "" && this.tabData[i].lib == this.selMarque) {
this.vehicules = this.tabData[i].modeles;
}
}
}
}
}
</script>
And here is the component file ListeFilter.vue :
<template>
<span class="relative">
<select
:name="nom"
:id="nom"
:label="label"
v-model="sel"
class="w-64 text-sm"
>
<option disabled value="">{{ label }}</option>
<option v-for="elem in liste" :value="elem.id" :key="elem.id">
{{ elem.libelle }}
</option>
</select>
<button
v-show="sel"
#click="reset"
>X</button>
</span>
</template>
<script>
export default {
props: ["nom", "label", "liste", "modelValue"],
emits: ["update:modelValue"],
computed: {
sel: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
methods: {
reset() {
this.sel = ""
},
},
};
</script>
I also made a Vue SFC Playground to test HERE
Thanks in advance for your help.
PS : if you know a component that does the same I take it ! (vue-select does not seem Vue 3 compliant)
I think you should be listening for the #update:modelValue="changefilter" event instead of the change event #change="changefilter".
It's documented here. https://v3-migration.vuejs.org/breaking-changes/v-model.html#overview
Try changing your App.vue to this.
Here is a Playground
<template>
<liste-filter
:nom="'marque'"
:label="'Choose a mark'"
:liste="marques"
v-model="selMarque"
#update:modelValue="changefilter"
>
</liste-filter>
<h1/>
<div v-for="vh in vehicules" :key="vh.lib" class="w-full">
{{ vh.lib }}
</div>
<div v-if="vehicules.length === 0">The list is empty</div>
</template>
<script>
import ListeFilter from "./ListeFilter.vue"
export default {
components: {
ListeFilter
},
data() {
return {
marques:[{id:"Renault",libelle:"Renault"},{id:"Peugeot",libelle:"Peugeot"}],
selMarque: "",
vehicules: [],
tabData:[{"lib":"Renault","modeles":[{"lib":"Clio"},{"lib":"Captur"}]},{"lib":"Peugeot","modeles":[{"lib":"208"},{"lib":"308"},{"lib":"3008"}]}]
};
},
methods: {
changefilter() {
console.log('change happened');
this.vehicules = [];
for (var i = 0; i < this.tabData.length; i++) {
if(this.selMarque != "" && this.tabData[i].lib == this.selMarque) {
this.vehicules = this.tabData[i].modeles;
}
}
}
}
}
</script>

Passing data from child component to parent and then to another child not working on page load but works after minor potentially unrelated change

I am new to Vuejs and come across this bug which I have no idea what I have done wrong. I am not receiving any console errors. It doesn't work on initial page load but it seems to work after I comment something out (or make a minor change). It will still then continue to work if I reverse the changes I just made and put it back to the original code. But once again on a fresh page load it won't work.
The issue: I am making a to do list and on page load when I add new tasks through the input field, the list does not appear on the page like it should be. I also console log the data array for this and it shows it is getting added to the array but is not getting rendered to the page. No console errors. In my code I will comment out some other data property (there are 2 additional ones below todosList in the TodoList.vue file that are currently not being used yet) and save and then the tasks will automatically appear on the page. So I think oh ok that might be the issue so with this new minor change I decide to refresh the page to see if it works as expected. Nope it doesn't so I then uncomment out what I previously commented out and save and the list appears again. But once again if I refresh the page it doesn't work. It only seems to be if I make a change inside the data function in the TodoList.vue file.
Additional info: The data is stored in the parent todos[] (App.vue), updated/pushed to array in a child (TodoCreate.vue) and sent back to the parent using $emit. This data is then sent through to another child (TodoList.vue) using props so that it can be rendered on the page.
Wondering if there is something that is not quite right in my code which is causing this to bug out like that. I will include everything in case it is something that looks unrelated to me but could be causing it.
Here is also a link to a code sandbox where the issue can be replicated by following the instructions on the page https://codesandbox.io/s/adding-new-todo-not-working-properly-jwwex?file=/src/components/TodoList.vue
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
App.vue
<template>
<div :class="currentMode">
<the-header #modeToggled="updateMode($event)"></the-header>
<main>
<todo-create #addedTodos="updateTodos"></todo-create>
<todo-list :todos="todos"></todo-list>
</main>
</div>
</template>
<script>
import TheHeader from './components/TheHeader.vue';
import TodoCreate from './components/TodoCreate.vue';
import TodoList from './components/TodoList.vue';
export default {
name: 'App',
components: {
TheHeader,
TodoCreate,
TodoList,
},
data() {
return {
currentMode: {
dark_mode: true,
light_mode: false
},
todos: [],
}
},
methods: {
updateMode(mode) {
this.currentMode = mode;
},
updateTodos(data) {
this.todos = data;
console.log(this.todos);
},
toggleCompleted() {
}
},
// provide() {
// return {
// todos: this.todos,
// };
// }
}
</script>
TheHeader.vue
<template>
<h1>To-do App</h1>
<div>
<label for="toggle-mode" aria-label="Toggle light and dark mode"></label>
<input type="checkbox" id="toggle-mode" #change="toggleMode">
</div>
</template>
<script>
export default {
emits: ['modeToggled'],
data() {
return {
toggleState: false,
}
},
methods: {
toggleMode() {
this.toggleState = !this.toggleState;
this.$emit('modeToggled', this.modeClasses);
}
},
computed: {
modeClasses() {
return {
dark_mode: !this.toggleState,
light_mode: this.toggleState
}
}
}
}
</script>
TodoCreate.vue
<template>
<div>
<label for="newtodo" class="sr-only">Create new to do</label>
<input type="text" id="newtodo" placeholder="Create a new todo..." v-model="todoval" v-on:keyup.enter="addTodo" >
</div>
</template>
<script>
export default {
emits: ['addedTodos'],
data() {
return {
todoval: '',
taskNumber: 0,
todos: [],
};
},
methods: {
addTodo() {
const val = this.todoval;
const taskNumber = this.taskNumber;
this.todos.push({ taskID: taskNumber, value: val, complete : 'not-completed'});
this.todoval = '';
this.taskNumber++;
console.log(this.todos);
this.$emit('addedTodos', this.todos);
},
}
}
</script>
TodoList.vue
<template>
<ul class="todo-items" :class="filterClass">
<li class="drop-zone" v-for="(listItem, index) in todosList" :class="listItem.complete" :key="listItem.taskID"
#drop='onDrop($event, index)'
#dragover.prevent
#dragenter.prevent>
<div class="drag-el" draggable="true"
#dragstart='startDrag($event, index)'>
<label :for="'checkbox-'+index" :aria-label="'Mark task ' + listItem.value + ' as completed'"></label>
<input type="checkbox" :id="'checkbox-'+index" #change="toggleCompleted(index, listItem.value, listItem.complete, listItem.taskID)">
<input type="text" disabled :value="listItem.value">
<img src="../assets/icon-cross.svg" #click="removeTask(index)">
</div>
</li>
</ul>
</template>
<script>
export default {
props: {
todos: Object,
filterClass: String
},
// inject: ['todos'],
data() {
return {
todosList: this.todos,
// completedTodos: [],
// activeTodos: [],
};
},
// watch: {
// todosList(data) {
// data.filter(function(todo) {
// if(todo.completed == 'completed') {
// completedTodos.push(todos);
// }
// });
// }
// },
methods: {
startDrag: (evt, item) => {
evt.dataTransfer.dropEffect = 'move'
evt.dataTransfer.effectAllowed = 'move'
evt.dataTransfer.setData('itemID', item)
},
onDrop (evt, list) {
const itemID = evt.dataTransfer.getData('itemID');
const movedData = this.todosList[itemID];
this.todosList.splice(itemID,1);
this.todosList.splice(list,0, movedData);
},
toggleCompleted() {
// still need to write this method
},
removeTask() {
// still need to write this method
}
}
}
</script>

How to make a function from another brother component be called when pressed?

Good afternoon, tell me please, I wrote a calendar displaying the events that the user sets, and now I want to make a detailed display of the event when I click it. The problem is that the events are in my component and the whole display logic is in another, how can I use them together.I want to make it so that the getDetailInformation() function in the ModalWindowDetail component component is called by clicking on an event in another component Calendar.vue. I use the event bus, but nothing works for me, I don’t understand why. Please help me solve this problem.
Screenshot of Calendar and error in console
Code of Calendar on GitHub
App.vue:
<template>
<div class="all">
<app-calendar #sendTextEvent="text = $event"></app-calendar>
<app-detail v-if="modalWindowDetail"
:eventText="text"></app-detail>
</div>
</template>
<script>
import appCalendar from './components/Calendar.vue'
import appDetail from './components/ModalWindowDetail.vue'
export default {
data(){
return{
text: String
}
},
components: {
appCalendar,
appDetail
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
}
}
};
</script>
Calendar.vue component which display calendar:
<template>
<div class="overflow-div">
<transition :name="nameOfClass" >
<div :key="currentPage" class="fade_wrapper">
<div v-for="(week, i) in getCalendar" class="d_day">
<li v-for="day in week" class="li_day">
<div class="day">{{ day }}</div>
<div v-for="event in buildEvents(i, day)"
class="event"
v-bind:class="{ 'eventBrown': eventBrown(event),
'eventPurple': eventPurple(event),
'eventOrange': eventOrange(event),
'eventBlue': eventBlue(event) }"
v-on:click="openModalDetail(event)">{{ event }}
</div>
</li>
</div>
</div>
</transition>
</div>
</template>
<script>
import json from './Calendar_data.json'
import { mapState } from "vuex";
import { eventBus } from './../main.js'
export default {
mounted(){
eventBus.$on('getDetailInformation', this.openModalDetail())
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
methods: {
openModalDetail(text){
this.$emit('sendTextEvent', text);
}
};
</script>
The component in which the getDetailInformation() is located ModalWindowDetail.vue:
<template>
<div class="underModalWindow">
<div class="modalWindow">
<img src="src/assets/x.png" width="20px" height="20px">
<div class="nameofModal">Вся детальная информация о событии</div>
<div v-for="(key, name) in eventDetail" class="detailEvent">{{ name }}: {{ key }}</div>
<button>Окей</button>
</div>
</div>
</template>
<script>
import { eventBus } from './../main.js'
export default {
props: ['eventText'],
data(){
return{
options: [
{ text: 'Встреча', value: '8' },
{ text: 'День Рождения', value: '4' },
{ text: 'Праздник', value: '1' },
{ text: 'Другое', value: '16' }
],
eventDetail: Object,
}
},
computed: {
eventsData() {
return this.$store.state.eventData;
},
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
created(){
eventBus.$emit('getDetailInformation', this.getDetailInformation())
},
methods: {
getDetailInformation(){
let arrOfEvents = this.eventsData.events;
for(let z = 0; z < arrOfEvents.length; z++){
let memo = arrOfEvents[z].memo;
console.log(this.memo)
if(memo === this.eventText){
let dataStartOfEvent = arrOfEvents[z].starts_at;
let getStartDataOfEvent = new Date(dataStartOfEvent);
let dataEndOfEvent = arrOfEvents[z].ends_at;
let getEndDataOfEvent = new Date(dataEndOfEvent);
if((getStartDataOfEvent.getHours() - 3) > 0){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleTimeString(),
'Конец события': getEndDataOfEvent.toLocaleTimeString(),
'Тип события': this.getType(arrOfEvents[z].type)
}
}else if(getStartDataOfEvent.getDate() != getEndDataOfEvent.getDate()){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleDateString(),
'Конец события': getEndDataOfEvent.toLocaleDateString(),
'Тип События': this.getType(arrOfEvents[z].type)
}
}
}
}
}
}
};
</script>
You should remove the () from the function name in eventBus.$on('getDetailInformation', this.openModalDetail()) - you want to reference the function, not to call it and use the result as a reference.
Also, your function getDetailInformation() does not return anything - but you seem to expect that it returns a text. You should correct this.
And finally, I think that #sendTextEvent="text = arguments[0]" would be more appropriate - and using a dedicated method/function will be the best.

How to create different menu page for each page

I am using Ionic 4, I want to do different menu content in different page. I have add menuId inside my code, but I only get the second page menu. My first page menu is missing.
I think you should build 1 menu and then just populate its page list depending on the page being visited.
So your code would look something like this:
import { Component, OnInit } from '#angular/core';
import { Router, RouterEvent } from '#angular/router';
#Component({
selector: 'app-menu',
templateUrl: './menu.page.html',
styleUrls: ['./menu.page.scss'],
})
export class MenuPage implements OnInit {
selectedPath = '';
pages = getPages();
constructor(private router: Router) {
this.router.events.subscribe((event: RouterEvent) => {
if (event && event.url) {
this.selectedPath = event.url;
}
});
}
getPages() : any[] {
if (this.router.url === '/page1') {
return [
{
title: 'First Page with Tabs',
url: '/menu/first'
},
{
title: 'Second Page blank',
url: '/menu/second'
}
];
}
if (this.router.url === '/page2') {
return [
{
title: 'First Page with Tabs',
url: '/menu/first'
},
{
title: 'Second Page blank',
url: '/menu/second'
}
];
}
}
ngOnInit() {
}
}
And then display it like this:
<ion-split-pane>
<ion-menu contentId="content">
<ion-header>
<ion-toolbar color="primary">
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-menu-toggle auto-hide="false" *ngFor="let p of pages">
<ion-item [routerLink]="p.url" routerDirection="root" [class.active-item]="selectedPath.startsWith(p.url)">
<ion-label>
{{ p.title }}
</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-item tappable routerLink="/login" routerDirection="root">
<ion-icon name="log-out" slot="start"></ion-icon>
Logout
</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-router-outlet id="content" main></ion-router-outlet>
</ion-split-pane>
Full tutorial about setting up a menu like this is available here:
How to Combine Ionic 4 Tabs and Side Menu Navigation [v4] - Ionic AcademyIonic Academy
Although the code I used as an example above for get getPages() is the bit you would write yourself.

v-for loop only array correspond to the dynamic page number

I store results per page number, see below:
<ul v-for="iten in listingsData" :key="item.id">
<li>{{ item.name }}</li>
</ul>
<button #click="pushPrev">Push Prev Results</button>
<button #click="pushNext">Push Next Results</button>
export default {
data(){
return {
listingsData : [],
page : 1
}
},
methods : {
pushNext(){
var _self = this;
axios.get('https://myapi.com/get/users?page='+this.page+1).then(function(response){
_self.page = _self.page + 1;
_self.listingsData = _self.listingsData.push({
page : _self.page,
results : response.data.results
})
});
},
pushPrev(){
var _self = this;
axios.get('https://myapi.com/get/users?page='+this.page-1).then(function(response){
_self.page = _self.page + 1;
_self.listingsData = _self.listingsData.push({
page : _self.page,
results : response.data.results
})
});
}
}
created(){
//load default data
var _self = this;
axios.get('https://myapi.com/get/users?page='+this.page).then(function(response){
_self.listingsData = {
page : 1,
results : response.data.results
}
});
}
}
Now how I can show or loop only results correspond to the the page number this.page?
_self.listingsData = _self.listingsData.push({
page : _self.page, // page number
results : response.data.results
})
What can I try?
I'm using Vue CLI and webpack.
You should iterate over computed property that will return specific page from listingsData, not over all pages.
Something like that:
<template>
<div v-if="currentPage">
<ul v-for="item in currentPage.results" :key="item.id">
<li>{{ item.name }}</li>
</ul>
</div>
<div v-else><i>Loading...</i></div>
<button #click="fetchPage(-1)" :disabled="page===1">Prev Results</button>
<button #click="fetchPage(1)" :disabled="page===10">Next Results</button>
</template>
<script>
const api = "https://myapi.com/get/users";
export default {
data() {
return {
page: 1,
listingsData: [],
};
},
created() {
this.fetchPage(0);
},
computed: {
currentPage() {
return this.listingsData.find(i => i.page === this.page);
},
},
methods: {
fetchPage(diff) {
this.page += diff;
if (this.currentPage) {
return; // page already loaded
}
const page = this.page;
axios.get(api, { params: { page } })
.then((res) => {
this.listingsData.push({
page,
results: res.data.results,
});
});
},
}
};
</script>
Here, we're loading page only if it hasn't been loaded before, and disable Prev/Next buttons if current page is 1/10 respectively.
Here is jsfiddle (with mockup data instead of actual API calls).