Screen Freezing when Setting Multiple Roots - aurelia

When my aurelia app starts I send them first to the login page and check and see if they are logged in and if so, set set the root to app, otherwise, have them log in.
aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName("modules/login")));
This should work, according to everything I could find. It does actually set the root to as far as the code is concerned as I see activity in the console, but the html on the screen never moves from the login screen. Even typing something manually in the address bar does not change the html. So it seems the router has stopped functioning. No errors are logged in the console.
import { AuthenticateStep, AuthService } from 'aurelia-authentication';
import { Router} from 'aurelia-router';
import { autoinject, PLATFORM, Aurelia } from "aurelia-framework";
#autoinject()
export class Login {
constructor(private router: Router, private authService: AuthService, private aurelia:Aurelia) {
console.log("Starting Login...")
}
activate() {
if (this.authService.authenticated) {
console.log("is authenticate")
this.router.navigate('/', { replace: true, trigger: false });
console.log("setting root to 'app'");
this.aurelia.setRoot(PLATFORM.moduleName("app"));
}
else {
console.log("not auth");
}
}
}
In app.ts
activate() {
console.log("app.activate");
...
}
Is there something else I should be doing?
I have tried: https://github.com/aurelia/framework/issues/400
And this: https://ilikekillnerds.com/2017/07/aurelia-routing-switching-root-using-setroot/

Here are a few things you can try:
Chain the promises (make sure the navigation is done before you tell aurelia to switch the root)
this.router.navigate('/', { replace: true, trigger: false })
then(() => this.aurelia.setRoot(PLATFORM.moduleName("app")));
Resolve the promises (would be necessary if the router still has work to do after the current activate because that work would need to be aborted)
return this.router.navigate('/', { replace: true, trigger: false })
then(() => this.aurelia.setRoot(PLATFORM.moduleName("app")));
Verify that the AppRouter is reconfigured after you switch root (breakpoint in configureRouter, you may need to manually .reset() the router is the isConfigured flag is somehow still true)
You could try a different approach altogether.
Personally when I need to switch root between a public and an authenticated shell, I just have a dedicated path prefix for either (or both) and in my main method I set the root to the correct App based on the current window.location.
Example (in main):
if (/\/public/.test(window.location.pathname)) {
au.setRoot(PLATFORM.moduleName("shell/public"));
} else if ((/\/admin/.test(window.location.pathname)) {
au.setRoot(PLATFORM.moduleName("shell/admin"));
} else {
au.setRoot(PLATFORM.moduleName("shell/app"));
}
Redirecting between these roots goes outside of the router, simply with window.location.href = "...";
Although it's arguably a little hacky, the nice thing about the approach is that you'll always have a completely clean Aurelia state after switching, and thus less you potentially need to clean up after.
In the non-public roots, you try to grab the auth token from localStorage and simply kick the user back to public if there is none (or they don't have sufficient privileges).

Setting the root is quite easy, but there is a caveat:
Set it either in response to a user generated event, or in the attached event.
Attempting to set it in the activated event or constructor will result in the screen becoming frozen on the root screen.
This took me pretty much a day to figure out, so I thought I would pass it on.
Here is what worked for me: I created a "app-shell" which is set to the root by main.
In app-shell, I check whether or not the person is already logged in, and then I set the root depending on the results.
import { AuthenticateStep, AuthService } from 'aurelia-authentication';
import { AppRouter } from 'aurelia-router';
import { autoinject, PLATFORM, Aurelia } from "aurelia-framework";
#autoinject()
export class AppShell {
constructor(private router: AppRouter, private authService: AuthService, private aurelia: Aurelia) {
}
attached() {
this.setRoot();
}
setRoot() {
this.router.navigate('/', { replace: true, trigger: false }); //Not actually needed here, but is if the router has already been configured.
if (this.authService.authenticated) {
this.aurelia.setRoot(PLATFORM.moduleName("app"));
}
else {
this.aurelia.setRoot(PLATFORM.moduleName("modules/login"));
}
}
}

Related

Vuex-module-decorator, modifying state inside an action

Using the vuex-module-decorator I have a authenticate action that should mutate the state.
#Action
public authenticate(email: string, password: string): Promise<Principal> {
this.principal = null;
return authenticator
.authenticate(email, password)
.then(auth => {
const principal = new Principal(auth.username);
this.context.commit('setPrincipal', principal);
return principal;
})
.catch(error => {
this.context.commit('setError', error);
return error;
});
}
// mutations for error and principal
But this fail with the following message:
Unhandled promise rejection Error: "ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an #Action?
That works only in dynamic modules.
If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]
What I don't understand is that it works well with #MutationAction and async. However I miss the return type Promise<Principal>.
#MutationAction
public async authenticate(email: string, password: string) {
this.principal = null;
try {
const auth = await authenticator.authenticate(email, password);
return { principal: new Principal(auth.username), error: null };
} catch (ex) {
const error = ex as Error;
return { principal: null, error };
}
}
--
At this time I feel blocked and would like to have some help to implement an #Action that can mutate the state and return a specific type in a Promise.
Just add rawError option to the annotation so it becomes
#Action({rawError: true})
And it display error normally. this is because the the library "vuex-module-decorators" wrap error so by doing this you will able to get a RawError that you can work with
You can vote down this answer if you would like because it isn't answering the specific question being posed. Instead, I am going to suggest that if you are using typescript, then don't use vuex. I have spent the past month trying to learn vue /vuex and typescript. The one thing I am committed to is using typescript because I am a firm believer in the benefits of using typescript. I will never use raw javascript again.
If somebody would have told me to not use vuex from the beginning, I would have saved myself 3 of the past 4 weeks. So I am here to try and share that insight with others.
The key is Vue 3's new ref implementation. It is what really changes the game for vuex and typescript. It allows us to not have to rely on vuex to automatically wrap state in a reactive. Instead, we can do that ourselves with the ref construct in vue 3. Here is a small example from my app that uses ref and a typescript class where I was expecting to use vuex in the past.
NOTE1: the one thing you lose when using this approach is vuex dev tools.
NOTE2: I might be biased as I am ported 25,000 lines of typescript (with 7000 unit tests) from Knockout.js to Vue. Knockout.js was all about providing Observables (Vue's ref) and binding. Looking back, it was kind of ahead of its time, but it didn't get the following and support.
Ok, lets create a vuex module class that doesn't use vuex. Put this in appStore.ts. To simplify it will just include the user info and the id of the club the user is logged into. A user can switch clubs so there is an action to do that.
export class AppClass {
public loaded: Ref<boolean>;
public userId: Ref<number>;
public userFirstName: Ref<string>;
public userLastName: Ref<string>;
// Getters are computed if you want to use them in components
public userName: Ref<string>;
constructor() {
this.loaded = ref(false);
initializeFromServer()
.then(info: SomeTypeWithSettingsFromServer) => {
this.userId = ref(info.userId);
this.userFirstName = ref(info.userFirstName);
this.userLastName = ref(info.userLastName);
this.userName = computed<string>(() =>
return this.userFirstName.value + ' ' + this.userLastName.value;
}
}
.catch(/* do some error handling here */);
}
private initializeFromServer(): Promise<SomeTypeWithSettingsFromServer> {
return axios.get('url').then((response) => response.data);
}
// This is a getter that you don't need to be reactive
public fullName(): string {
return this.userFirstName.value + ' ' + this.userLastName.value;
}
public switchToClub(clubId: number): Promise<any> {
return axios.post('switch url')
.then((data: clubInfo) => {
// do some processing here
}
.catch(// do some error handling here);
}
}
export appModule = new AppClass();
Then when you want to access appModule anywhere, you end up doing this:
import { appModule } from 'AppStore';
...
if (appModule.loaded.value) {
const userName = appModule.fullName();
}
or in a compositionApi based component. This is what would replace mapActions etc.
<script lang="ts">
import { defineComponent } from '#vue/composition-api';
import { appModule } from '#/store/appStore';
import footer from './footer/footer.vue';
export default defineComponent({
name: 'App',
components: { sfooter: footer },
props: {},
setup() {
return { ...appModule }
}
});
</script>
and now you can use userId, userFirstName, userName etc in your template.
Hope that helps.
I just added the computed getter. I need to test if that is really needed. It might not be needed because you might be able to just reference fullName() in your template and since fullName() references the .value variables of the other refs, fullName might become a reference itself. But I have to check that out first.
I sugest this simple solution, work fine for me 👌:
// In SomeClassComponent.vue
import { getModule } from "vuex-module-decorators";
import YourModule from "#/store/YourModule";
someMethod() {
const moduleStore = getModule(YourModule, this.$store);
moduleStore.someAction();
}
If the action has parameters, put them.
Taken from: https://github.com/championswimmer/vuex-module-decorators/issues/86#issuecomment-464027359

Angular 5: Route animations for navigating to the same route, but different parameters

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!

Aurelia - Update the menubar once a user has logged out

I have navmenu that needs to reloaded after a user logs out.
I have a logout.ts that essentially clears the JWT and loggedIn value.
import { autoinject } from "aurelia-framework";
import { TokenService } from "../../auth/tokenService"; z
import { Router } from 'aurelia-router';
#autoinject
export class Logout {
constructor(private tokenService: TokenService, public router: Router) {
tokenService.clearJWT();
this.router.refreshNavigation()
}
}
Thats all fine but I wanted to redirect to the home page but at the same time update the menu this time rechecking for loggedIn status.
I tried redirect, I have tried:
this.router.navigateToRoute('home')
and the one above. In all cases the navmenu does not update. By updating the navmenu it will check for a loggedin value in localstorage and change the structure of the menu.
I also wanted it to go the home page after removing those items but more importantly how do I get it to refresh the navmenu?
It sounds like you need to make sure your home route is refreshed even though it is already the current route. If so, in your configureRouter method, add activationStrategy.replace:
import {activationStrategy} from 'aurelia-router';
export class MyClass {
configureRouter(config) {
config.map([{
route: 'home',
name: 'home',
activationStrategy: activationStrategy.replace,
title: 'My Title',
moduleId: 'myModule',
}]);
}
}

How to avoid/fix "Auth0Lock is not defined" exception

I am trying to use the Auth0 for social login but I keep getting an exception of an undefined reference.
This is the authentication service
import { Injectable } from '#angular/core';
import { tokenNotExpired } from 'angular2-jwt';
// Avoid name not found warnings
declare var Auth0Lock: any;
#Injectable()
export class AuthService {
// Configure Auth0
lock = new Auth0Lock('I have set the ID correctly here', 'and the domain as well', {});
constructor() {
// Add callback for lock `authenticated` event
this.lock.on("authenticated", (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
});
}
public login() {
// Call the show method to display the widget.
this.lock.show();
};
public authenticated() {
// Check if there's an unexpired JWT
// This searches for an item in localStorage with key == 'id_token'
return tokenNotExpired();
};
public logout() {
// Remove token from localStorage
localStorage.removeItem('id_token');
};
}
I injected the services and configured providers. Everything is wired correctly, but it just won't find Auth0Lock even though defined.
Each time it reaches lock = new Auth0Lock('ID', 'DOMAIN', {}); it bombs out.
I replaced declare var Auth0Lock: any; with const Auth0Lock = require('auth0-lock').default; and that fixed the problem.
The accepted answer is good. I did get a Cannot find name 'require' error.
Rather than using 'declare const require', I imported like so:
import Auth0Lock from 'auth0-lock';
I needed to add to index.html:
<script src="https://cdn.auth0.com/js/lock/10.8/lock.min.js"></script>
via https://github.com/auth0/lock/issues/588

Auth2 Object unidentified when trying to sign out (Angular2)

Good Day,
I am trying to sign out an auth2 client. This process was working fine before I upgraded my router to fit in with new RC requirements. Now it seems as if the auth2 object is cleared or lost along the way from signing in to signing out.
Here is my sign out tag:
<a role="button" (click)="signOut()" style="padding-left: 30px;">Log out</a>
it simply calls a signOut() function found in navbar.component.ts (See below)
signOut() {
var auth2 = this._navigationService.getAuth2();
auth2.signOut().then(function () {
});
console.log('User signed out.');
sessionStorage.clear();
localStorage.clear();
this.router.navigate(['Login'])
window.location.reload()
}
here is the navigationService code it is calling:
import { Injectable } from '#angular/core';
#Injectable()
export class NavigationService {
onEditMode:boolean;
auth2:any;
constructor() {
this.onEditMode=true;
}
getEditMode(){
return this.onEditMode;
}
setEditMode(editMode:boolean){
this.onEditMode=editMode;
}
setAuth2(auth2:any){
this.auth2=auth2;
}
getAuth2(){
return this.auth2;
}
}
Here is my login.component.ts which sets the auth2 object seen in navigationService.ts:
onGoogleLoginSuccess = (loggedInUser) => {
this.isLoading=true;
console.log(loggedInUser)
this._navigationService.setAuth2(gapi.auth2.getAuthInstance());
console.log("Google gapi" + gapi.auth2.getAuthInstance());
sessionStorage.setItem('gapi',gapi.auth2.getAuthInstance());
this._zone.run(() => {
this.userAuthToken = loggedInUser.hg.access_token;
this.userDisplayName = loggedInUser.getBasicProfile().getName();
var strClientID = document.getElementsByTagName('meta')['google-signin-client_id'].getAttribute('content')
this.objTrimbleAuthentication.ClientID = document.getElementsByTagName('meta')['google-signin-client_id'].getAttribute('content');
this.objTrimbleAuthentication.IDToken = loggedInUser.getAuthResponse().id_token;
this._trimbleAuthenticationService.sendAndVerify(this.objTrimbleAuthentication).subscribe(data=>{
if(data.tokenIsValid==true){
sessionStorage.setItem('S_USER_EMAIL',loggedInUser.getBasicProfile().getEmail());
sessionStorage.setItem('S_USER_NAME',loggedInUser.getBasicProfile().getName());
sessionStorage.setItem('S_ID_TOKEN',this.userAuthToken);
this.objExternalBindingModel.ExternalAccessToken=this.userAuthToken;
this.objExternalBindingModel.Provider="Google";
this.objExternalBindingModel.UserName = loggedInUser.getBasicProfile().getName();
this._LoginService.obtainLocalAccessToken(this.objExternalBindingModel).subscribe(data=>{
// console.log(data);
this.isLoading=false;
this._router.navigate(['/Home']);
sessionStorage.setItem("access_token",data.access_token);
},error=>{
console.log(error);
})
}else{
this.isLoading= false;
this.showModal('#trimbleAuthError');
}
}, error=>{
})
});
}
onGoogleLoginSuccess is called from login.component.html:
<div style="margin-left:8% !important" id="{{googleLoginButtonId}}"></div>
So this process was working fine until I update my router to use the latest Angular2 Release Candidate. I am out of ideas on what could possibly be causing the following error when I click the sign out button:
Error in component.html/navbar.component.html:12:33
ORIGINAL EXCEPTION: TypeError: Cannot read property 'signOut' of undefined
if you need any other information or components please ask I hope I have given enough information. As I said it was working so keep that in mind, please.
Update
Waiting for additional info ...
In the following code, auth2:any; is undeclared. Is setAuth2 called anywhere before signOut()?
import { Injectable } from '#angular/core';
#Injectable()
export class NavigationService {
onEditMode:boolean;
auth2:any;
constructor() {
this.onEditMode=true;
}
getEditMode(){
return this.onEditMode;
}
setEditMode(editMode:boolean){
this.onEditMode=editMode;
}
setAuth2(auth2:any){
this.auth2=auth2;
}
getAuth2(){
return this.auth2;
}
}
Base on limited information and code posted, my guess is a logical bug in the logout process.
In signOut(), the window.location.reload() reload the page at the current url, which also clear all variables/objects. However, after reload, your app properly try to do signout again (due to url?).
In your navbar.component, you may need to add more logic in ngInit() to handle the situation.
Or can your code work without window.location.reload()? It seems odd to use that with angular2, especially with routing.
Right, the solution i found to the above question was that signing out using localhost will not work. So i just used this block of code when deploying the website and keep it commented out when running the website on localhost.
this is my signOut() function found in navbar.component.ts:
signOut() {
//////////////////////////////////////// Uncomment block for live deployment //////////////////////////////
// var auth2 = gapi.auth2.getAuthInstance();
// auth2.signOut().then(function () {
// console.log('User signed out.');
// });
//////////////////////////////////////////////////////////////////////////////////////////////////////////
sessionStorage.clear();
localStorage.clear();
this.router.navigate(['/']);
window.location.reload();
}
although getAuthInstance gives an error when trying to run it in localhost, deploying the web application to a server seems to work fine.