Aurelia - Navmenu not updating based on @computed

2019-08-25 11:49发布

问题:

I received and answer from THIS question which I think is correct however I do not get a value propagating back to the view. I have added in a number of console.logs and they are not being hit.

Here is my navMenu - I wanted "loggedIn" to be true if it has a value in localstorage...

        <template bindable="router">
        <require from="./navmenu.css"></require>
        <div class="main-nav">
            <div class="navbar navbar-inverse">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="#/home">Jobsledger.API</a>
                </div>
                <div class="navbar-collapse collapse">

                    <ul class="nav navbar-nav">

                        <li repeat.for="row of router.navigation" class="${ row.isActive ? 'link-active' : '' }">
                            <a href.bind="row.href" if.bind="loggedIn">${ row.title }</a>

                            <a href.bind="row.href" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"
                               if.bind="loggedIn">
                                ${row.title}
                                <span class="caret"></span>
                            </a>

                            <ul if.bind="loggedIn" class="dropdown-menu">
                                <li repeat.for="menu of row.settings.nav">
                                    <a href.bind="menu.href">${menu.title}</a>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
        test ${loggedIn}
    </template>

Here is the navmenu.ts viewmodel showing the @computedFrom and the getter:

            import { autoinject, inject, NewInstance } from "aurelia-framework";
        import { computedFrom } from "aurelia-binding";
        import { bindable } from "aurelia-templating";

        import { LoggedInService } from "../components/auth/LoggedInService";

        @autoinject
        export class Navmenu {
            constructor(private testAuthentication: LoggedInService) {
                console.log("loggedin value: ", this.testAuthentication.isAuthenticated())
            }

            @computedFrom('testAuthentication.authenticated')
            get loggedIn() {
                console.log("loggedin value: ", this.testAuthentication.isAuthenticated())
                return this.testAuthentication.isAuthenticated()
            }
        }

Here is my loggedIn class - pretty simple - goes off and gets a value in local storage - if its not there it returns false.

         export class LoggedInService {
          private LOGGED_IN = "loggedIn";
          constructor() {}

          isAuthenticated(): boolean {
            var authenticated = localStorage.getItem(this.LOGGED_IN);

            console.log("LOGGEDIN - typeof: ", authenticated);

            if (authenticated != null) {
              return true;
            } else return false;
          }
        }

None of my console.logs are displayed in the console which means none of those functions are being hit.. I dont know why.. should be pretty simple..

Could someone have a look over this and direct me to why its still not working?

UPDATE

I appreciate Jeremy taking a look and I have had a go at implementing this. Here are my updated classes.

I have changed the names on the classes but they represent what Jeremy has suggested. Here they are in the same order he presented them...

Here is my version of his "user.ts":

        export class LoggedInService {
        private LOGGED_IN = "loggedIn";

        isLoggedIn: boolean = false;
        userName: string = 'anonymous';

        constructor() { }

        isAuthenticated(): boolean {
            var authenticated = localStorage.getItem(this.LOGGED_IN);

            console.log("LOGGEDIN value HERE!: ", authenticated);

            if (authenticated != null) {
                return true;
            } else {
                return false;
            }
        }
    }

I have "isLoggedIn" set as 'false' and "username" set to 'anonymous'.

Here is my version of "navmenu.ts":

    import { autoinject } from "aurelia-framework";
    import { bindable } from "aurelia-templating";
    import { LoggedInService } from "../components/auth/LoggedInService";

    @autoinject
    export class Navmenu {

        constructor(public loggedInService: LoggedInService) { }
    }

I assume this should give my navmenu.html - the view - access to those values and here I am uncertain how they should be accessed in the if.bind. I just wanted to test it using:

 <div if.bind="loggedInService.isLoggedIn">
        working
    </div>

this didnt work.

Finally here is my version of auth_service.ts. This is where the fetch is undertaken to login - I have set the value of loggedIn and username in the fetch:

    constructor(
    private login: userLogin,
    private loggedinService: LoggedInService,
    private tokenService: TokenService,
    private userService: UserService,
    private router: Router,
    private http: HttpClient,
    private controllerFactory: ValidationControllerFactory
) {
    this.router = router;
    this.controller = controllerFactory.createForCurrentScope();
    this.controller.addRenderer(new BootstrapFormRenderer());


    // Check if user is logged in
    if (this.loggedinService.isAuthenticated()) {
        loggedinService.isLoggedIn = true;
        loggedinService.userName = this.userService.getUserName();
    }
}

submitLogin() {
    if (this.controller.validate()) {
        // Lets do a fetch!

        this.login.Username = this.username;
        this.login.Password = this.password;

        const task = fetch("/api/jwt", {
            method: "POST",
            body: JSON.stringify(this.login),
            headers: new Headers({ 'content-type': 'application/json' })
        })
            .then(response => response.json())
            .then(data => {
                // First save the JWT and as well save it to loggedInService.
                this.loggedinService.isLoggedIn = this.tokenService.saveJWT(data);

                // Next go back to the api and get the username and save that to loggedInService also.
                this.loggedinService.userName = this.userService.saveUserName();

                // Finally redirect to home page.
                this.router.navigate("home");
            })
            .catch(error => {
                this.tokenService.clearJWT();
            });
    }
}

I still dont get any kind of activity in the navmenu view.. not sure if I have set this ok wondering if someone can take another look at this.. maybe I havent referenced it correctly in the navbar view. I note that loggedInService is a dependency both in the navmenu and also the login.ts viewmodels. Shouldnt this work or have screwed something up....

回答1:

You could model this slightly differently and avoid all the dirty checking and computedFrom stuff.

Create a model for your current user:

user.ts

export class User {
  isLoggedIn = false;
  name = 'anonymous';
}

Then take a dependency on the user in view models that need access to the isLoggedIn state or the user's details:

navmenu.ts

@autoinject
export class Navmenu {
  constructor(public user: User) { }
}

Services that do the login work or the local storage work also take a dependency on the current user, updating it's properties when the login state changes.

auth-service.ts

@autoinject
export class AuthService {
  constructor(private user: User) { 
     if (... check local storage, etc ... ) {
       user.isLoggedIn = true;
       user.name = ....
     }
  }

  login(username, password) {
    return fetch('/api/login', { method: 'POST', body: { ... } })
      .then(result => {
         ... 
         ...
         this.user.isLoggedIn = true;
         this.user.name = username;
         ...
      });
  }
}


回答2:

This is probably because you didnt use your view model at all. Try check if you reference to the module navmenu view model, or its view navmenu.html