Art_0f_War
Art_0f_War

Reputation: 119

How to update Blazor static layout from code

I have a main layout with a sidebar that differs for authorized and non-authorized users. The place which I want to update looks like that

        <AuthorizeView>
            <Authorized>
                // Personal information matches in this component (it's just one more div this some code in it)
                <UserInfo />
            </Authorized>
            <NotAuthorized>
                <div class="sidebar-unathorized">
                    <span>
                        To get all privileges, <a href="/register"><strong>register</strong></a> or <a href="/login"><strong>login</strong></a> please
                    </span>
                </div>
            </NotAuthorized>
        </AuthorizeView>

After user passes authorization I want him to see his personal information so in my login method I do some stuff like

public async void HandleValidSubmit()
{
    ...
   ((CustomAuthenticationStateProvider)authenticationStateProvider).AuthenticateUser(authorizedUser);
   navigationManager.NavigateTo("/");
   //here I want to update the layout
    ...
    return;
}

and in my CustomAuthenticationStateProvider after setting the current user I do NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); which i hoped would be enough to all components which are authorization-based to update. But it's not. I tried StateHasChanged() method but understanably it doesn't work like that cause it just updates the component which it's triggered from. But if u manually reload the page after logging in all will be ok. Any ideas how can I update the layout from code?

Upvotes: 3

Views: 6214

Answers (2)

Sith2021
Sith2021

Reputation: 3716

You don't need DI, if the class that generates the event is static. An example. Let's say you want to swap the language from a component, in NavMenu. Create a class AppStatus in root

public static class AppStatus
{
    public static event Action OnChange;
    // sample
    public static string Culture { get; set; } = "en-US";

    public static void UpdateLanguage() => NotifyStateChanged();

    static void NotifyStateChanged() => OnChange?.Invoke();
}

NavMenu.razor:

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">Blazor Multilanguage @AppStatus.Culture</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>
// ...
// add item
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="set-lang">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Set Language
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;
    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    private void ToggleNavMenu() => collapseNavMenu = !collapseNavMenu;
    // subscribe event 
    protected override void OnInitialized() => AppStatus.OnChange += StateHasChanged;
    public void Dispose() => AppStatus.OnChange -= StateHasChanged;
}

Create component SetLanguage.razor:

@page "/set-lang"
<h3>SetLanguage</h3>
<button class="btn btn-primary" @onclick="ChangeLanguage">Toggle Language</button>

@code {
    void ChangeLanguage()
    {
        AppStatus.Culture = AppStatus.Culture == "en-US" ? "ru-RU": "en-US";
        AppStatus.UpdateLanguage();
    }
}

When clicked the button, then NavMenu update the state... I only illustrated the part that solves the question consulted

Upvotes: 0

enet
enet

Reputation: 45596

I'm not sure about the layout of your MainLayout, so let us suppose for this answer's shake that the AuthorizeView component is embedded within the NavMenu component, itself embedded in the MainLayout component...

You want to refresh the content of the NavMenu component, which is embedded in the MainLayout component, from the login page, right?

You can use various methods to achieve this. The following solution is based on the App State Pattern.

First off, we have to create a service class that can be accessed from both, the NavMenu component and the Login component. Here's the class:

public class AppState
{
private bool _loggedIn;
public event Action OnChange;
public bool LoggedIn
{
    get { return _loggedIn; }
    set {
        if (_loggedIn != value)
        {
            _loggedIn = value;
            NotifyStateChanged();
        }
    }
 }

 private void NotifyStateChanged() => OnChange?.Invoke();
}

This class defines an event delegate, named OnChange, which should encapsulate the method that will refresh the NavMenu. This delegate is invoked when the boolean property LoggedIn's value changes. The LoggedIn property's value may change in the Login page, when the user has been logged in, thus any subscriber to this delegate, in our case, the NavMenu, will be notified of this.

Login Page

  • @inject AppState AppState Note the above inject the AppState to the Login Page. Put it at the top of the page

  • AppState.LoggedIn = true; that code should be place at the end of the log in procedure. This will initiate the triggering of the OnChange delegate.

NavMenu component

  • @inject AppState AppState
  • @implements IDisposable

*

protected override void OnInitialized()
{
    AppState.OnChange += StateHasChanged;
}

public void Dispose()
{
    AppState.OnChange -= StateHasChanged;
}

Now, whenever you log in, the AppState service notifies the NavMenu component to re-render so that the content of the Authorized in the AuthorizeView is rendered.

Startup class

services.AddSingleton<AppState>();

Upvotes: 14

Related Questions