Update components element from a different component

Viewed 809

Im trying to change the classes of an element inside one of my components from a page, but the classes never change. I put a Console.Writeline inside the function, and the function is definitley running.

The CircleClass variable by default is set to "danger", and that works properly, the div starts off with the danger class. The console.writeline confirms that the variable is being changed, it just isn't updating the element.

Code in component:

<div class="circle @CircleClass"></div>

@code {
    private string CircleClass = "danger";

    public void ChangeStatus(int status)
    {
        switch (status)
        {
            case 0:
                CircleClass = "idle";
                break;
            case 1:
                CircleClass = "safety";
                break;
            case 2:
                CircleClass = "danger";
                break;
        }

        Console.WriteLine($"{status} Circle: {CircleClass}");
    }
}

First Attempt

@page "/"

@Inject Nav nav

<button @onclick="@(e => nav.ChangeStatus(2))">Two</button>

In Startup.cs I added this:

services.AddSingleton<Nav>();

Second Attempt

@page "/"

<button @onclick="@(e => ChangeStatus(2))">Two</button>

private void ChangeStatus(int status)
{
    Nav nav = new Nav();
    nav.ChangeStatus(status);
}

I think it may have something to do with it creating a new instance of the class which isn't able to change the classes of the elements I need changing, although I believe that should be solved when using AddSingleton.

Anyone know what could be causing this issue?


Calling the function inside of the component confirms that the function actually works.

Component:

protected override void OnInitialized()
{
    ChangeStatus(1);
}

What I am trying:

Nav.razor

<li id="status">
    <div class="circle @CircleClass"></div>
</li>

@code {
    private string CircleClass = "danger";

    public void ChangeStatus(int status)
    {
        switch (status)
        {
            case 0:
                CircleClass = "idle";
                break;
            case 1:
                CircleClass = "safety";
                break;
            case 2:
                CircleClass = "danger";
                break;
            default:
                CircleClass = "idle";
                break;
        }

        Console.WriteLine($"{status} Circle: {CircleClass}");

        InvokeAsync(() =>
        {
            StateHasChanged();
        });
    }
}

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();

    services.AddSingleton<Nav>();
}

Index.razor

@page "/"

@inject Nav nav 

<button @onclick="@(e => nav.ChangeStatus(0))">Zero</button>
<button @onclick="@(e => nav.ChangeStatus(1))">One</button>
<button @onclick="@(e => nav.ChangeStatus(2))">Two</button>

In my code examples above, I am using buttons to try to run functions in the nav component from a page, but eventually I am going to run these functions from different places that wont be pages without direct user input.


The Nav component is defined/created in my MainLayout, it isn't directly applied to the Index page.

2 Answers

You need to make a service that can help manage the component.

Firstly:

  • Make the service and move the function that updates the component to it (in this case ChangeStatus()).
  • Add an event to your new service and trigger it at the end of the ChangeStatus() function.

services/MyService.cs

using System;

namespace MyAppName.Services
{
    public delegate void MyEventHandler();

    public class MyService
    {
        public event MyEventHandler MyEvent;
        public string CircleClass = "danger";

        public void ChangeStatus(int status)
        {
            switch (status)
            {
                case 0:
                    CircleClass = "idle";
                    break;
                case 1:
                    CircleClass = "safety";
                    break;
                case 2:
                    CircleClass = "danger";
                    break;
            }

            // Fire event
            MyEvent?.Invoke();
        }
    }
}

Secondly:

  • Add service as a Singleton in your blazor app.
  • For easier access, add the Services namespace to your _Imports.razor file.

Adding service

// In Blazor Server-Side
// open Startup.cs and add:

public void ConfigureServices(IServiceCollection services)
{
    // [..]

    services.AddSingleton<MyService>();
}

// In Blazor WebAssembly
// open Program.cs and add:

public static async Task Main(string[] args)
{
    // [..]

    builder.Services.AddSingleton<MyService>();
}

Add service to _Imports.razor

// This will only work if the namespace
// for your service is the MyAppName.Services.
// If you put your service in the Services folder
// you should be fine unless you renamed the namespace.
@using MyAppName.Services

Thirdly:

  • Inject service into your component.
  • Subscribe to the event in your component and create a function to run StateHasChanges() when the event fires.

Component.razor

// At the top:
@inject MyService myService

// Subscribe to event and create function
@code {
    protected override void OnInitialized()
    {
        myService.MyEvent+= UpdateState;
    }

    private void UpdateState()
    {
        // Need to use InvokeAsync, atleast
        // on Blazor Server-Side
        InvokeAsync(() =>
        {
            StateHasChanged();
        });
    }
}

Finally:

  • Run ChangeStatus() function in the service from somewhere else (in this case, from a page)

Index.razor

@page "/"

@inject MyService myService

<button @onclick="@(e => myService.ChangeStatus(0))">Test 0</button>
<button @onclick="@(e => myService.ChangeStatus(1))">Test 1</button>

You can do something like this. I think you don't need Nav service. You can call and change class value from page itself.

Inside index page

HTML

<Nav @ref="navComp"></Nav>

<button @onclick="@(e => navComp.ChangeStatus(0))">Zero</button>
<button @onclick="@(e => navComp.ChangeStatus(1))">One</button>
<button @onclick="@(e => navComp.ChangeStatus(2))">Two</button>

C# Code

@code {
Nav navComp;

    //other code
}

Inside Nav component

HTML

<h3>Nav</h3>
<li id="status">
    <div class="circle @CircleClass"></div>
</li>

C# Code

@code {
    private string CircleClass = "danger";

    public void ChangeStatus(int status)
    {
        switch (status)
        {
            case 0:
                CircleClass = "idle";
                break;
            case 1:
                CircleClass = "safety";
                break;
            case 2:
                CircleClass = "danger";
                break;
            default:
                CircleClass = "idle";
                break;
        }

        Console.WriteLine($"{status} Circle: {CircleClass}");

        InvokeAsync(() =>
        {
            StateHasChanged();
        });
    }
}

you can see the output right here..

output

Related