I've work on a Vue project, I have a login page which redirect to the Home page when the user is logged.
The thing is that I need to update/re-render the header component when the user is on the Home page.
So I've created a global variable in the main.ts:
Main.ts
Vue.prototype.isLogin = false;
I use this global value as my key for my header:
App.vue
<template>
<div id="app" class="container">
<e-header v-bind:key="isLogin" />
<div class="alert-box">
<div class="alert-list">
<e-alert
v-for="(notif, index) in $store.state.notifications"
:key="index"
:type="notif.type"
#dismissAlert="dismissAlert(index)"
>
{{ notif.message }}
</e-alert>
</div>
</div>
<router-view />
</div>
</template>
And on the Login component, in my login() methods:
Login.vue
AdminApi.login(this.email, this.password).then(() => {
this.loaderActive = false;
this.isLogin = true;
});
The problem is when the user login successfully and redirected on the Home page, the header component doesn't update, do I need to use prop instead of a global variable in my App.vue?
Vue updates it stuff when it detects that the data it depends on changes. For Vue to detect that it changes, the data needs to be reactive.
Something on the prototype chain is not, and I think you are over-complicating things by using the prototype chain for that. To manage a global state, just use a Vuex store. (docs)
You would then use ...mapGetters(['isLoggedIn']) in your computed property, this.$store.commit('loggedIn', true) or something along those lines in your Login.vue file.
Related
I am working with Vuejs. I want to render components based on value of variable val.
My component looks like this
<template v-if="this.$val===1">
<component1 />
</template>
<template v-if="this.$val===2">
<component2 />
</template>
I have defined a global variable val using Vue.prototype and I am updating it using onclick function,where I am changing value of val to 2 but after clicking it doesnt show component2 instead of component 1.
Define val globally in main.js using following line of code
Vue.prototype.$val = 1;
Can someone please help me with this. Thanks
td,dr; Vue.prototypeis not reactive.
I'm going to enumerate issues as I observe them, hoping you'll find them useful.
You're not specifying which version of Vue you're using. Since you're using Vue.prototype, I'm going to guess you're using Vue 2.
Never use this in a <template>.
Inside templates, this is implicit (sometimes formulated: "inside templates this doesn't exist"). What would be this.stuff in controller, is stuff in the template.
You can't conditionally swap the top level <template> of a Vue component. You need to take the conditional either one level up or one level down:
one level up would be: you create separate components, one for each template; declare them and have the v-if in their parent component, rendering one, or the other
one level down would be: you move the v-if inside the top level <template> tag of the component. Example:
<template><!-- top level can't have `v-if` -->
<div v-if="val === 1">
val is 1
<input v-model="val">
</div>
<div v-else>
val is not 1
<input v-model="val">
</div>
</template>
<script>
export default {
data: () => ({ val: 1 })
}
</script>
Note <template> tags don't render an actual tag. They're just virtual containers which help you logically organise/group their contents, but what gets rendered is only their contents.1 So I could have written the above as:
<template><!-- top level can't have v-if -->
<template v-if="val === 1">
<div>
val is 1
<input v-model="val">
</div>
</template>
<template v-else>
<template>
<template>
<div>
val is not 1
<input v-model="val">
</div>
</template>
</template>
</template>
</template>
And get the exact same DOM output.
For obvious reasons, <template> tags become useful when you're working with HTML structures needing to meet particular parent/child constraints (e.g: ul + li, tr + td, tbody + tr, etc...).
They're also useful when combining v-if with v-for, since you can't place both on a single element (Vue needs to know which structural directive has priority, since applying them in different order could produce different results).
Working example with what you're trying to achieve:
Vue.prototype.$state = Vue.observable({ foo: true })
Vue.component('component_1', {
template: `
<div>
This is <code>component_1</code>.
<pre v-text="$state"/>
<button #click="$state.foo = false">Switch</button>
</div>
`})
Vue.component('component_2', {
template: `
<div>
This is <code>component_2</code>.
<pre v-text="$state"/>
<button #click="$state.foo = true">Switch back</button>
</div>
`})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.7.10/dist/vue.min.js"></script>
<div id="app">
<component_1 v-if="$state.foo"></component_1>
<component_2 v-else></component_2>
</div>
Notes:
<div id="app">...</div> acts as <template> for the app instance (which is, also, a Vue component)
Technically, I could have written that template as:
<div id="app">
<template v-if="$state.foo">
<component_1 />
</template>
<template v-else>
<component_2 />
</template>
</div>
, which is pretty close to what you were trying. But it would be slightly more verbose than what I used, without any benefit.
I'm using a Vue.observable()2 for $state because you can't re-assign a Vue global. I mean, you can, but the change will only affect Vue instances created after the change, not the ones already created (including current one). In other words, Vue.prototype is not reactive. This, most likely, answers your question.
To get past the problem, I placed a reactive object on Vue.prototype, which can be updated without being replaced: $state.
1 - there might be an exception to this rule: when you place text nodes inside a <template>, a <div> wrapper might be created to hold the text node(s). This behaviour might not be consistent across Vue versions.
2 - Vue.Observable() was added in 2.6.0. It's a stand-alone export of Vue's reactivity module (like a component's data(), but without the component). In v3.x Vue.Observable() was renamed Vue.reactive(), to avoid confusion/conflation with rxjs's Observable.
global variables are accessed in template without this keyword which means $val===1 will work.
Solution 1:
<template>
<component1 v-if='$val === 1' />
<component2 v-else/>
</template>
This will work.
But you could make use of dynamic components in your case.
Solution 2:
<template>
<component :is='currentComponent'/>
</template>
<script>
\\imports of your components will go here
export default {
name: 'app',
components: {
component1, component2
},
computed:{
currentComponent(){
return this.$val === 1?component1:component2;
}
}
}
</script>
Dynamic components are more performant and helps you maintain state of component.
Actually I have my project like this.
Views
Home.vue
Classroom.vue
App.vue
In the Home.vue I have my the content and the login form. Basically, if you are not connected yet you have the submit the login (if the login is correct then isAuth = true) and the form hide and you can now see the content (list of classrooms)
<div v-if="isAuth" #click="goToClassroomView"> List of classrooms </div>
<div v-if="!isAuth"> Here is the login form... </div>
In my App.vue, I have an app-bar and main with a router-view with my logout function.
<v-app-bar app color="#2196f3">
<v-btn #click="logout()">logout</logout>
</v-app-bar>
<v-main class="white">
<router-view></router-view> //Either Home.vue or Classroom.vue
</v-main>
Here is my logout function inside the App.vue :
logout() {
this.$store.dispatch('LOGOUT')
.then(() => {
this.$router.push('/') //go to home.vue
})
}
So here comes my problem. When I click on the logout button when i'm in the home.app, the user logout correctly but my page is now a empty list of classrooms (because there's no user and isAuth is a variable inside the Home.vue so I can't affect it from the App.vue).
So is it possible to reload the page when I'm in the Home page or access to isAuth from the App.vue, so when an user logout in the home page, the list of classroom disappear and it shows the form ?
You can change isAuth variable to vuex store state.
Then in LOGOUT action you change isAuth state to false.
I try to create a custom component for my application with emberJS, I have followed the quickstart tutoriel and now I try to create a upload button as a component.
It seems I don't have code issues but my component don't render on my main page. I have use the ember component generator to create it
here my upload-button.hbs :
<button type="button">{{#buttonText}}</button>
now my upload-button.js :
import Component from '#ember/component';
export default Component.extend({
actions: {
showExplorer(){
alert()
}
}
});
for the moment I simply put a alert() method in showExplorer()
and now my main page, application.hbs :
<upload-button #buttonText="Uploader un fichier" {{action "showExplorer"}}/>
{{outlet}}
I expect to see my button but I just have a blank page with no code error.
I suspect that's a really stupid mistake but I can't find it.
Glad you decided to try out Ember.js :) Your upload-button.hbs and upload-button.js file looks good. However, there are a couple of issues here.
The component, when invoked using the angle-bracket syntax (<... />), the name should be CamelCased to distinguish between HTML tags and Ember components. Hence, we need to invoke the upload-button component as <UploadButton />.
You defined your action, showExplorer, inside the component (upload-button.js) file, but, referenced in the application.hbs file. The data in the backing component file can only be accessed inside the component's .hbs file because of the isolated nature of the component architecture. Also, we can only attach an action using {{action}} modifier to a DOM element and not to the component itself. So,
we need to remove the action binding from application.hbs file,
{{!-- application.hbs --}}
<UploadButton #buttonText="Uploader un fichier"/>
and add the same inside the upload-button.hbs file:
{{!-- upload-button.hbs --}}
<button type="button" {{action "showExplorer"}}>{{#buttonText}}</button>
A working model can be found in this CodeSandbox
Hope you find learning Ember, a fun process!
In my initial App.vue I'm dispatching an action that checks the authentication of a user. The component looks as follows:
<template>
<v-app>
<spinner v-if="!user" :status="spinner.status" :color="spinner.color" :size="spinner.size" :depth="spinner.depth" :rotation="spinner.rotation" :speed="spinner.speed"></spinner>
<div v-if="style">
<app-header></app-header>
<v-content style="margin-left:24px;margin-right:24px;">
<!-- <v-container grid-list-md text-cs-center> -->
<router-view v-if="user"></router-view>
<!-- </v-container> -->
</v-content>
<app-footer></app-footer>
</div>
</v-app>
</template>
In this component I add the component. This will render as soon as we know the auth status.
Once the auth status has been determined (logged in/out) I dispatch another action which is responsible for fetching styles from the server. The admin user gets a generic style while a normal user would get a styling based on a dynamic value in the URL.
Eg: yoursite.com/company_one would fetch styles from the server where the company is company_one
When I try to access the router in my store.js file I receive the following output in the console.
console.log(router.history) =>
I now want to drill down to get the agency parameter so I try:
If I
console.log(router.history.current);
Which gives me the following output in the console:
All of the sudden all properties are empty. If I drill further to get to the agency, the console outputs undefined
Can somebody explain why this happens?
If you're looking for the current params, you can try this.$route.params like described here: https://router.vuejs.org/guide/essentials/dynamic-matching.html
I have a certain div class (landing), that I only want to have appear on certain routes. I am using a "main" template that has my header and footer (with a >yield included between for each pages unique code). However, there is an element in the header that I only want to appear on the landing page ('/' route).
I am using Iron Router.
Is there a way to do this easily with an #if in meteor? Thanks!
Here's a generic route name equality testing helper:
Template.registerHelper('routeEquals',function(name){
return Router.current().route.getName() === name;
});
Then in any template you can do (for example):
{{#if routeEquals '/'}}
in the / route
{{else}}
Not in the / route
{{/if}}
You want to create a helper on your header template that reactively checks the current route. I haven't used Iron Router in a while, but believe Router.current().route.getName() is reactive. Try the following:
Template.header.helpers({
onLanding() {
return Router.current().route.getName() === '<landingRouteName>';
},
});
<template name="header">
{{#if onLanding}}
<div>I should only show on the landing page!</div>
{{/if}}
</template>
Or, alternatively (and maybe optimally - it would keep the template loaded, rather than injecting it into the DOM when you hit landing), you can use the helper to apply a class like
<div class="{{#if onLanding}}isVisible{{/if}}>...</div>
You can put a session flag in the onBeforeAction of the landing route and then, on the header, check if that session is valid and show the div
Something like this
this.route("landing", {
path: "/landing",
onBeforeAction: function () {
Session.set('showDiv', true);
}
});
<template name='main'>
<header>{{#if helper_getShowDivFlag}}<div></div>{{/if}}</header>
</template>
helper_getShowDivFlag: function() {
return Session.get('showDiv');
}
Alternatively, checking the router name in the main template and show/hide the div
<template name='main'>
<header>{{#if helper_showDivFlag}}<div></div>{{/if}}</header>
</template>
helper_showDivFlag: function() {
return Router.current().route.getName() == 'landing'
}
Or if you don't want to write code, use this package and check the router :D
<template name='main'>
<header>{{#if isActiveRoute 'landing' }}<div></div>{{/if}}</header>
</template>
You can accomplish this using the data property of your route.
Router.route('/', {
name: 'home',
data: function() {
return {
showMySpecialHeaderWidget: true
};
}
});
Then you can access this data property in your template.
<template name="main">
<header>
{{#if showMySpecialHeaderWidget}}
<div>My special header widget</div>
{{/if}}
</header>
{{> yield}}
</template>
Using this method, you don't need to worry about updating your template if you ever change the names of your routes, and it's easy to show your widget on multiple routes by including this data property.