How to pass async data to child components in an object (Angular6) - javascript

I'm trying to display data retrieved from a server (using Angular 6, Rxjs and Chartjs), and render a chart using the data.
If I use local mock data, everything renders just fine. But if I use get the data from the servers, the necessary data to render the graphs isn't available so the charts render as blank charts.
Summary:
A component makes a service call, and prepares an object to pass down to a child component using the response from the service call. However, by the time the response is ready, the object is already sent without the necessary information.
Service code snippet:
getAccountsOfClientId(clientID: string): Observable<Account[]> {
return this.http.get<Account[]>(`${this.BASE_URL}/accounts?client=${clientID}`)
.pipe(
tap(accounts => console.log('fetched client\'s accounts')),
catchError(this.handleError('getAccountsOfClientId', []))
);
}
In client-info.component.ts (component to make the service call, and prepare and pass the object to child component)
#Input() client; // received from another component, data is filled
constructor(private clientAccountService: ClientAccountService) { }
ngOnInit() {
this.getAccountsOfClientId(this.client.id);
}
ngAfterViewInit() {
this.updateChart(); // render for pie chart
this.updateBarChart(); // render for bar chart
}
getAccountsOfClientId(clientID: string): void {
this.clientAccountService.getAccountsOfClientId(this.client.id)
.subscribe(accounts => this.clientAccounts = accounts);
}
updateBarChart(updatedOption?: any): void {
/* unrelated operations above ... */
// Create new base bar chart object
this.barChart = {};
this.barChart.type = 'bar';
this.setBarChartData();
this.setBarChartOptions('Account', 'Balance');
}
setBarChartData(): void {
// field declarations..
console.log('clientAccounts has length: ' + this.clientAccounts.length); // prints 0
this.clientAccounts.map((account, index) => {
// do stuff
});
dataset = {
label: 'Balance',
data: data,
...
};
datasets.push(dataset);
// since clientAccounts was empty at the time this function ran, the "dataset" object doesn't contain
// the necessary information for the chart to render
this.barChart.data = {
labels: labels,
datasets: datasets
};
}
I'm looking for changes using ngOnChanges (in the child component), however the chart data is NOT updated in the child component after the "clientAccounts" array is filled with the response.
#Input() chart: Chart;
#Input() canvasID: string;
#Input() accountBalanceStatus: string;
ngOnChanges(changes: SimpleChanges) {
if (changes['accountBalanceStatus'] || changes['chart']) {
this.renderChart();
}
}
renderChart(): void {
const element = this.el.nativeElement.querySelector(`#${this.canvasID}`);
if (element) {
const context = element.getContext('2d');
if (this.activeChart !== null) {
this.activeChart.destroy();
}
this.activeChart = new Chart(context, {
type: this.chart.type,
data: this.chart.data,
options: this.chart.options
});
} else {
console.log('*** Not rendering bar chart yet ***');
}
}
Can you point me to how I should continue my research on this?
Sorry for the long question, and thanks!
EDIT: Upon request, the templates are below
Parent (client-info):
<div class='client-info-container'>
<div class='info-container'>
<li>Date of Birth: {{ client.birthday | date: 'dd/MM/yyyy' }}</li>
<li>Name: {{ client.name }}</li>
<li>First Name: {{ client.firstname }}</li>
</div>
<div class='more-button'>
<button (click)='openModal()'>More</button>
</div>
<div class='chart-container'>
<div *ngIf='pieChart && client'>
<app-balance-pie-chart
[chart]='pieChart'
[canvasID]='accountBalancePieChartCanvasID'
(updateChart)='handlePieChartOnClick($event)'>
</app-balance-pie-chart>
</div>
<div class='bar-chart-container'>
<div class='checkbox-container'>
<div *ngFor='let option of cardTypeCheckboxOptions' class='checkbox-item'>
<input
type='checkbox'
name='cardTypeCheckboxOptions'
value='{{option.value}}'
[checked]='option.checked'
[(ngModel)]='option.checked'
(change)="updateCardTypeCheckboxSelection(option, $event)"/>
<p>{{ option.name }} {{ option.checked }}</p>
</div>
</div>
<div *ngIf='barChart && client'>
<!-- *ngIf='client.accounts.length === 0' -->
<div class="warning-text">This client does not have any accounts.</div>
<!-- *ngIf='client.accounts.length > 0' -->
<div>
<app-balance-account-bar-chart
[chart]='barChart'
[canvasID]='accountBarChartCanvasID'
[accountBalanceStatus]='accountBalanceStatus'>
</app-balance-account-bar-chart>
</div>
</div>
</div>
</div>
</div>
Chart:
<div class='bar-chart-canvas-container' *ngIf='chart'>
<canvas id='{{canvasID}}' #{{canvasID}}></canvas>
</div>

ngOnChanges(changes: SimpleChanges) {
if (changes['accountBalanceStatus'] || changes['chart']) {
this.renderChart();
}
}
ngOnChanges's argument value is type of SimpleChanges for each Input()
prop:
class SimpleChange {
constructor(previousValue: any, currentValue: any, firstChange: boolean)
previousValue: any
currentValue: any
firstChange: boolean
isFirstChange(): boolean
}
You should check you data by previousValue, currentValue.
Something like:
if(changes.accountBalanceStatus.previousValue != changes.accountBalanceStatus.currentValue
|| changes.chart.previousValue != changes.chart.currentValue){
this.renderChart();
}
StackBlitz Demo

I saw that, you are not assigning the data directly to this.barChart instead you are assigning it as this.barChart.data, which means you are modifying the property directly, which might not invoke the ngOnChanges of the child component. This is due to the explanation that you have given in your comments.
I read that it may be because angular change detection checks the
differences by looking at the object references
And it will not get to know when the property of object gets changed
The variable that is bound to #Input() property is this.barChart and not this.barChart.data.
Instead of
this.barChart.data = {
labels: labels,
datasets: datasets
};
You try this
this.barChart = {
data : {
labels: labels,
datasets: datasets
}};
here you are directly modifying this.barChart which should trigger ngOnChanges().
EDIT :
You should be invoking this.updateChart(); inside subscribe block of
this.clientAccountService.getAccountsOfClientId(this.client.id)
.subscribe((accounts) => {
this.clientAccounts = accounts;
this.updateChart();
})
That is why you also have this.clientAccounts.length as 0

Your component needs to have the data before rendering. You may use resolve, a built in feature that Angular provides to handle use-cases like the ones you described.
Also look here. may be a useful resource in a tutorial form.

You need to interact with child components from parent this you nned to use input binding.
Refer:
https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding

My issue is solved and I'd like to share the solution in case anyone needs it in the future. As Amit Chigadani suggested (in the comments), invoking my chart updating functions in the subscribe block worked.
getAccountsOfClientId(clientID: string): void {
this.clientAccountService.getAccountsOfClientId(this.client.id)
.subscribe(accounts => {
this.clientAccounts = accounts;
this.updateChart();
this.updateBarChart();
});
}

Related

Vue with VCalendar component: Keep data in sync with another Vue instance

I'm programming a page that displays a list of meetings in a table. It's also possible to edit and delete meetings. Now I'd like to offer an alternative view using VCalendar.
Data is received from the server on page load and stored in a JS variable. Both the Vue instance containing the table and the VCalendar component share this data. If I edit a table cell, the changes are reflected in the component. But when I delete a date in the table view, it remains in the calendar.
This is the relevant HTML (edit: Added some attributes to the td):
<calendar-component></calendar-component>
<table id='meetings-table'>
<tr v-for='meeting in meetings' :key='date.id'>
<td contenteditable #blur='handleInput($event,meeting,"name")>
#{{ meeting.name }}
</td>
<td>
<input type='checkbox' v-model='selected'
:value='meeting.id'>
</td>
</tr>
</table>
<div>
<button v-if='selected.length' #click='deleteMeetings'>
Delete selected rows
</button>
</div>
My JS (edit: Added handleInput method):
let table = new Vue({
el:'#meetings-table',
data: {
selected: [],
meetings: window.meetings,
},
methods: {
/**
* Deletes selected meetings.
*/
deleteMeetings: function () {
let requests = [];
// Make a single request and store it
for (let id of this.selected) {
requests.push(axios.delete('/termine/' + id)
.then(response => {
// Remove meetings
this.meetings = this.meetings.filter(t => t.id != id);
// Remove id from list of selected meetings
this.selected = this.selected.filter(elem => elem != id);
}));
}
const axiosArray = axios.all(requests);
},
/**
* Handles edits in table cells.
*/
handleInput: function($event, meeting, field) {
const newValue = $event.target.textContent;
// Update value in $data
meeting[field] = newValue;
// AJAX request follows, but is not necessary for this example to work
}
}
});
The relevant parts of the component:
<template>
<v-calendar :attributes='attributes'>
<div
slot='meeting-row'
slot-scope='{ customData }'>
<!-- Popover content omitted -->
</div>
</v-calendar>
</template>
<script>
let meetings = window.meetings;
export default {
data() {
return {
incId: meetings.length,
editId: 0,
meetings,
};
},
computed: {
attributes() {
return [
// Today attribute
{
// ...
},
// Meeting attributes
...this.meetings.map(meeting => ({
key: meeting.id,
dates: new Date('2018,11,31'),// moment(meeting.slot.date, 'DD.MM.YY').format('YYYY, MM, DD'), //meeting.dates,
customData: meeting,
order: meeting.id,
dot: {
backgroundColor: '#ff8080',
},
popover: {
// Matches slot from above
slot: 'meeting-row',
}
}))
];
}
}
};
</script>
This is what happens:
I load the page containing only a single meeting. The meeting is
shown both in the table and the calendar component. Vue devtools show
it in both meetings arrays (in the component as well as in the other
Vue instance). Using the console, I can also see it in
window.meetings.
After clicking the delete button (triggering the deleteMeetings method in my JS), the meeting is gone from the table, but remains in
the calendar, in the component's meetings array and in
window.meetings.
What do I have to change to keep the meetings arrays in sync even when deleting a meeting in the table? Note that I haven't yet implemented any methods for the calendar component.
Calendar, and table components should share a single state: currently selected meetings. From what I understand, right now you have that state in 2 separate places: table Vue instance, and a calendar-component, which is a child of some other Vue instance.
It may look like you're sharing the state already (with window.meetings), but it's not the case: you only initialize the same set of meetings when the components are created. And then changes in one component are not reflected in another component.
What you can try to do is to have meetings stored in the 'main' Vue app on your page, pass them as props to table and calendar components, and then trigger events from table and calendar components, when meetings array is modified. You should also define the event hanlders in the 'main' Vue app, and listen on components. A rough sketch of the solution:
<div id="app">
<table-component
:meetings="meetings"
#meetingUpdated="handleMeetingUpdate"
#meetingDeleted="handleMeetingDeletion"
></table-component>
<calendar-component
:meetings="meetings"
#meetingUpdate="handleMeetingUpdate"
#meetingDeleted="handleMeetingDeletion"
></calendar-component>
</div>
let app = new Vue({
el:'#app',
data: {
meetings: []
},
methods: {
handleMeetingUpdate(event) {
//
},
handleMeetingDeletion(event) {
//
},
}
//
});
I hope the above is enough to point you in the right direction. If not, please let me know, and I'll do my best to help you with this further.

Render label from Model based on language selected

I am making a multi-lingual app using angular. I am showing a list of categories.
The api response is like this:
[
{
name_en: 'Watches',
name_ar: 'راقب'
},
{
name_en: 'Toys',
name_ar: 'ألعاب الأطفال'
}
]
I am looping on this array to show :
<label *ngFor='let item of items'>{{item.name_en}}</label>
On Arabic language select, I want to show name_ar in label instead of name_en. How can this be achieved ? I can also have more then two languages.
PS: I can always pass the language to server and retrieve the field as per current language but I am looking at a solution to do this dynamically and bring all fields in one time
In case of Static content You can use some kind of translate pipe or your custom one.
But in case of dynamic binding like you did in your example, Either you can use some global methods to check condition for your selected language like this -
<label *ngFor='let item of items'>{{parseLanguage(item)}}</label>
parseLanguage(item) {
if(this.selectedLang == 'X'){ return item.name_ar}
else return item.name_en
}
Or another way is to get an only single response from the server as per the language selected.
Update
I have created one pipe for the same which accept whole object containing values of diff. languages and return as per language selected, Hope this works for you -
import { Component, NgModule, Pipe, PipeTransform } from '#angular/core'
#Pipe({ name: 'translate'})
export class TranslatePipe implements PipeTransform {
languageSelected: string;
constructor() { }
transform(value) {
console.log(value, 'in pipe');
// Made a check for global language selected and return accordingly
if(this.languageSelected == 'arabic'){
return value.name_ar;
}
else {
return value.name_en;
}
}
}
<p *ngFor='let item of items'>
{{item | translate}}
</p>
Working example
i think you should save your label name in data base and server side and when you send request for give retrieve the field data, label names is coming in json and run *ngfor on this.
json in server side :
form1 = { ar : [
{
name: 'راقب'
},
{
name: 'ألعاب الأطفال'
}
], en : [
{
name: 'Watches'
},
{
name: 'Toys'
}
], ... another lang]
form2 = {} and another form
var listOfLabel = fom1['langComingFromClintSide'];
response.send(listOfLabel);
and you code change :
<label *ngFor='let item of items'>{{item.name}}</label>

How can I access object from component in my constant?

I have a component that has several functions that subscribes to data returned from an observable in my service. This is working properly, but I would like to refactor my code so I can access that data in my constant and simply loop through the data in my view.
The problem I'm having is when I want to read a property on my object from my constant e.g. value: this.price.market_price_usd, I get a undefined error in my console. I've also tried to interpolate data by doing something like value: '${this.price.market_price_usd}',. This simply just returns a string. It appears the problem is that my constant is not aware of this data. How can I refactor my code so I can access this data in my constant and read it in my template? Below is the relevant code.
Component
constructor(private data: DataService) {
this.loadstate = data.loadstate;
this.subscription = data.nameChange.subscribe((value) => {
this.loadstate = value;
});
}
getPrice() {
this.data.getData(this.API_Price)
.subscribe(
price => this.price = price,
error => this.errorMessage_price = <any>error);
}
getBlockSize() {
this.data.getData(this.API_Block_Size)
.subscribe(
size => this.size = size.toFixed(2),
error => this.errorMessage_size = <any>error);
}
getTransactions() {
this.data.getData(this.API_Transactions)
.subscribe(
transactions => this.transactions = transactions.values.slice(-1)[0].y.toLocaleString(),
error => this.errorMessage_transactions = <any>error);
}
getMempool() {
this.data.getData(this.API_Mempool)
.subscribe(
mempool => this.mempool = Math.trunc(mempool.values[0].y).toLocaleString(),
error => this.errorMessage_mempool = <any>error);
}
Constant
export const statsConstant = {
STATS: {
ERROR: {
message_1: 'There is a problem connecting to the API.',
message_2: 'Please wait a moment and try again.'
},
ROW_1: [
{
title: 'Market Price USD',
value: this.price.market_price_usd,
error: 'errorMessage_price',
errorMessage_1: 'There is a problem connecting to the API.',
errorMessage_2: 'Please wait a moment and try again.',
subtitle: 'Average USD market price across major bitcoin exchanges',
symbol: 'USD'
},
{
title: 'Average Block Size',
value: this.size,
error: 'errorMessage_size',
errorMessage_1: 'There is a problem connecting to the API.',
errorMessage_2: 'Please wait a moment and try again.',
subtitle: 'The 24 hour average block size in MB.',
symbol: 'Megabytes'
}
],
ROW_2: [
{
title: 'Transactions per Day',
subtitle: 'The aggregate number of confirmed Bitcoin transactions in the past 24 hours.'
},
{
title: 'Mempool Size',
subtitle: 'The aggregate size of transactions waiting to be confirmed.'
}
]
}
}
Template
<div class="row">
<div
class="col-sm-6 stats"
*ngFor="let stat of stats.STATS.ROW_1">
<div class="u-centerX">
<h5>{{stat.title}}</h5>
</div>
<div class="u-centerX">
<div
class="alert alert-danger" role="alert"
*ngIf="stat.error === true">
<p>{{stat.errorMessage_1}}.</p>
<p>{{stat.errorMessage_2}}.</p>
</div>
</div>
<div class="u-centerX">
<div
*ngIf="loadstate"
class="loader">
</div>
<div
*ngIf="price.market_price_usd"
class="stats-data">
<a href="https://blockchain.info/charts/market-price">
<h1>${{stat.value}}</h1>
<span>{{stat.symbol}}</span>
</a>
</div>
</div>
<div class="u-centerX">
<p>{{stat.subtitle}}</p>
</div>
</div>
</div>
You can't use a constant for dynamically changing data. A constant - as its name tells - is constant and can't receive value changes. But as you are requesting your values asynchronously, you have value changes for sure.
But the solution should be simple:
Don't declare statsConstant as const but as a property of your Components' class. Now values can be mutated when your async calls return.
Initialize the dynamic values (e.g. stats.value) with empty values. I would choose null here.
Overwrite the emtpy values with the actual values as soon as your async calls return with the data. After that your view should update automatically.
After that there should be no more need to additionally assign this.price and this.size.
You spoke about refactoring. My suggestion would be to move all that stats data structure and fetching it to an own service. Your component could then only use this single service API to get the whole stats data. Your service would be responsible for building it up. This would be cleaner in my opinion.

rendering vue.js components and passing in data

I'm having trouble figuring out how to render a parent component, display a list of contracts in a list on part of the page, and when a user clicks on one of them, display the details of that specific contract on the other part of the page.
Here is my slim file:
#contracts_area
.filter-section
ul
li.filter-item v-for="contract in contractsAry" :key="contract.id" #click="showContract(contract)"
| {{ contract.name }}
.display-section
component :is="currentView" transition="fade" transition-mode="out-in"
script type="text/x-template" id="manage-contracts-template"
div
h1 Blank when page is newly loaded for now
script type="text/x-template" id="view-contract-template"
div :apply_contract="showContract"
h1#display-item__name v-name="name"
javascript:
Vue.component('manage-template', {
template: '#manage-contracts-template'
});
Vue.component('view-contract', {
template: '#view-contract-template',
props: ['show_contract'],
data: function() {
return {
name: ''
}
},
methods: {
showContract: function(contract) {
return this.name = contract.name
}
}
});
Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content');
var contractsResource = Vue.resource('/all_contracts{/id}.json');
var contracts = new Vue({
el: '#contracts_area',
data: {
currentView: 'manage-template',
contractsAry: [],
errors: {}
},
mounted: function() {
var that = this;
contractsResource.get().then(
function(res) {
that.contractsAry = res.data;
}
)
},
methods: {
showContract: function(contract) {
this.currentView = 'view-contract'
}
}
});
Basically I'd like it so that when a user clicks on any contract item in the .filter-section, it shows the data for that contract in the .display-section. How can I achieve this?
In short you can bind a value to a prop.
.display-section
component :is="currentView" :contract="currentContract"
view-contract
props: ['contract']
contracts-area
data: {
currentContract: null,
},
methods: {
showContract: function(contract) {
this.currentView = "view-contract";
this.currentContract = contract;
}
}
There are multiple ways to pass data in Vue.
Binding values to props.
Using ref to directly call a method from a child component.
Custom Events. Note that to pass events globally, you will need a global event bus.
A single central source of truth (i.e. vuex)
I have illustrated methods 1, 2, 3 in Codepen
Note that 2nd and 3rd methods will only work after your component has been rendered. In your case, since your components for currentView are dynamic and when user clicked, display-section component does not yet exists; it will not receive any events yet. So their content will be empty at first.
To workaround this you can directly access $parent in mounted() from child component, however this would create coupling between them. Another solution is creating the components but conditionally displaying them. And one another solution would be waiting until child component has been mounted and then emitting events.
If your needs are simple I suggest binding values to props (1), else you may consider using something like vuex.

Passing data to components in vue.js

I'm struggling to understand how to pass data between components in vue.js. I have read through the docs several times and looked at many vue related questions and tutorials, but I'm still not getting it.
To wrap my head around this, I am hoping for help completing a pretty simple example
display a list of users in one component (done)
send the user data to a new component when a link is clicked (done) - see update at bottom.
edit user data and send it back to original component (haven't gotten this far)
Here is a fiddle, which fails on step two: https://jsfiddle.net/retrogradeMT/d1a8hps0/
I understand that I need to use props to pass data to the new component, but I'm not sure how to functionally do it. How do I bind the data to the new component?
HTML:
<div id="page-content">
<router-view></router-view>
</div>
<template id="userBlock" >
<ul>
<li v-for="user in users">{{user.name}} - <a v-link="{ path: '/new' }"> Show new component</a>
</li>
</ul>
</template>
<template id="newtemp" :name ="{{user.name}}">
<form>
<label>Name: </label><input v-model="name">
<input type="submit" value="Submit">
</form>
</template>
js for main component:
Vue.component('app-page', {
template: '#userBlock',
data: function() {
return{
users: []
}
},
ready: function () {
this.fetchUsers();
},
methods: {
fetchUsers: function(){
var users = [
{
id: 1,
name: 'tom'
},
{
id: 2,
name: 'brian'
},
{
id: 3,
name: 'sam'
},
];
this.$set('users', users);
}
}
})
JS for second component:
Vue.component('newtemp', {
template: '#newtemp',
props: 'name',
data: function() {
return {
name: name,
}
},
})
UPDATE
Ok, I've got the second step figured out. Here is a new fiddle showing the progress: https://jsfiddle.net/retrogradeMT/9pffnmjp/
Because I'm using Vue-router, I don't use props to send the data to a new component. Instead, I need set params on the v-link and then use a transition hook to accept it.
V-link changes see named routes in vue-router docs:
<a v-link="{ name: 'new', params: { name: user.name }}"> Show new component</a>
Then on the component, add data to the route options see transition hooks:
Vue.component('newtemp', {
template: '#newtemp',
route: {
data: function(transition) {
transition.next({
// saving the id which is passed in url
name: transition.to.params.name
});
}
},
data: function() {
return {
name:name,
}
},
})
-------------Following is applicable only to Vue 1 --------------
Passing data can be done in multiple ways. The method depends on the type of use.
If you want to pass data from your html while you add a new component. That is done using props.
<my-component prop-name="value"></my-component>
This prop value will be available to your component only if you add the prop name prop-name to your props attribute.
When data is passed from a component to another component because of some dynamic or static event. That is done by using event dispatchers and broadcasters. So for example if you have a component structure like this:
<my-parent>
<my-child-A></my-child-A>
<my-child-B></my-child-B>
</my-parent>
And you want to send data from <my-child-A> to <my-child-B> then in <my-child-A> you will have to dispatch an event:
this.$dispatch('event_name', data);
This event will travel all the way up the parent chain. And from whichever parent you have a branch toward <my-child-B> you broadcast the event along with the data. So in the parent:
events:{
'event_name' : function(data){
this.$broadcast('event_name', data);
},
Now this broadcast will travel down the child chain. And at whichever child you want to grab the event, in our case <my-child-B> we will add another event:
events: {
'event_name' : function(data){
// Your code.
},
},
The third way to pass data is through parameters in v-links. This method is used when components chains are completely destroyed or in cases when the URI changes. And i can see you already understand them.
Decide what type of data communication you want, and choose appropriately.
The best way to send data from a parent component to a child is using props.
Passing data from parent to child via props
Declare props (array or object) in the child
Pass it to the child via <child :name="variableOnParent">
See demo below:
Vue.component('child-comp', {
props: ['message'], // declare the props
template: '<p>At child-comp, using props in the template: {{ message }}</p>',
mounted: function () {
console.log('The props are also available in JS:', this.message);
}
})
new Vue({
el: '#app',
data: {
variableAtParent: 'DATA FROM PARENT!'
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<p>At Parent: {{ variableAtParent }}<br>And is reactive (edit it) <input v-model="variableAtParent"></p>
<child-comp :message="variableAtParent"></child-comp>
</div>
I think the issue is here:
<template id="newtemp" :name ="{{user.name}}">
When you prefix the prop with : you are indicating to Vue that it is a variable, not a string. So you don't need the {{}} around user.name. Try:
<template id="newtemp" :name ="user.name">
EDIT-----
The above is true, but the bigger issue here is that when you change the URL and go to a new route, the original component disappears. In order to have the second component edit the parent data, the second component would need to be a child component of the first one, or just a part of the same component.
The above-mentioned responses work well but if you want to pass data between 2 sibling components, then the event bus can also be used.
Check out this blog which would help you understand better.
supppose for 2 components : CompA & CompB having same parent and main.js for setting up main vue app. For passing data from CompA to CompB without involving parent component you can do the following.
in main.js file, declare a separate global Vue instance, that will be event bus.
export const bus = new Vue();
In CompA, where the event is generated : you have to emit the event to bus.
methods: {
somethingHappened (){
bus.$emit('changedSomething', 'new data');
}
}
Now the task is to listen the emitted event, so, in CompB, you can listen like.
created (){
bus.$on('changedSomething', (newData) => {
console.log(newData);
})
}
Advantages:
Less & Clean code.
Parent should not involve in passing down data from 1 child comp to another ( as the number of children grows, it will become hard to maintain )
Follows pub-sub approach.
I've found a way to pass parent data to component scope in Vue, i think it's a little a bit of a hack but maybe this will help you.
1) Reference data in Vue Instance as an external object (data : dataObj)
2) Then in the data return function in the child component just return parentScope = dataObj and voila. Now you cann do things like {{ parentScope.prop }} and will work like a charm.
Good Luck!
I access main properties using $root.
Vue.component("example", {
template: `<div>$root.message</div>`
});
...
<example></example>
A global JS variable (object) can be used to pass data between components. Example: Passing data from Ammlogin.vue to Options.vue. In Ammlogin.vue rspData is set to the response from the server. In Options.vue the response from the server is made available via rspData.
index.html:
<script>
var rspData; // global - transfer data between components
</script>
Ammlogin.vue:
....
export default {
data: function() {return vueData},
methods: {
login: function(event){
event.preventDefault(); // otherwise the page is submitted...
vueData.errortxt = "";
axios.post('http://vueamm...../actions.php', { action: this.$data.action, user: this.$data.user, password: this.$data.password})
.then(function (response) {
vueData.user = '';
vueData.password = '';
// activate v-link via JS click...
// JSON.parse is not needed because it is already an object
if (response.data.result === "ok") {
rspData = response.data; // set global rspData
document.getElementById("loginid").click();
} else {
vueData.errortxt = "Felaktig avändare eller lösenord!"
}
})
.catch(function (error) {
// Wu oh! Something went wrong
vueData.errortxt = error.message;
});
},
....
Options.vue:
<template>
<main-layout>
<p>Alternativ</p>
<p>Resultat: {{rspData.result}}</p>
<p>Meddelande: {{rspData.data}}</p>
<v-link href='/'>Logga ut</v-link>
</main-layout>
</template>
<script>
import MainLayout from '../layouts/Main.vue'
import VLink from '../components/VLink.vue'
var optData = { rspData: rspData}; // rspData is global
export default {
data: function() {return optData},
components: {
MainLayout,
VLink
}
}
</script>

Categories

Resources