Auto update ViewComponent on view

6.8k Views Asked by At

I currently have this on my view:

Cart

But whenever I add something to the cart, it won't auto-update the count. Only whenever I refresh the page.

How can I make it so it auto-updates? It is with a View Component on the _Layout view.

CartViewComponent

public class CartViewComponent : ViewComponent
    {
        private readonly ApplicationDbContext _context;
        private readonly SignInManager<UserModel> _signInManager;
        private readonly UserManager<UserModel> _userManager;

        public CartViewComponent(
            ApplicationDbContext context, 
            SignInManager<UserModel> signInManager, 
            UserManager<UserModel> userManager)
        {
            _context = context;
            _signInManager = signInManager;
            _userManager = userManager;
        }

        public int CheckIfOrderExists()
        {
            var userId = Request.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);

            OrderMain orderMain = _context.OrderMains.Where(o => o.user_id == userId).FirstOrDefault();
            if (orderMain != null)
            {
                return orderMain.id;
            }
            else
            {
                return 0;
            }

        }

        public int CountItemsInShoppingCart(int order_id)
        {
            var result = (from orderlines in _context.OrderLines
                          join items in _context.Items on orderlines.item_id equals items.id
                          join ordermains in _context.OrderMains on orderlines.order_id equals ordermains.id
                          where orderlines.order_id == order_id
                          select orderlines).Count();

            return result;
        }

        public IViewComponentResult Invoke()
        {
            int order_id = CheckIfOrderExists();
            int count = CountItemsInShoppingCart(order_id);
            ViewBag.count = count;

            return View(ViewBag.count);
        }
    }

Default.cshtml

<span class="badge">@ViewBag.count</span>

_Layout.cshtml

...
<li>
    <a asp-area="" asp-controller="ShoppingCart" asp-action="Index">
        <span style="color:black;" class="glyphicon glyphicon-shopping-cart"></span>
        @await Component.InvokeAsync("Cart")
    </a>
</li>
...
2

There are 2 best solutions below

0
On BEST ANSWER

I actually did it with a jQuery get function, instead of with View Components. In the get function I retrieve the public int TotalCount() from my ShoppingCartController.

On the _Layout partial view I created the jQuery script, one for rendering the current value and one for updating the value every 2 seconds:

<script src="~/lib/jquery/dist/jquery.min.js">
</script>
<!-- rendering the current value -->
<script>
    $.get('/ShoppingCart/TotalCount', function (data) {
        $('#countCart').html(data);
    });
</script>
<!-- reloading the value every 2 seconds -->
<script>
    setInterval(function () {
        $('#countCart').load('/ShoppingCart/TotalCount');
    }, 2000)
</script>

And the badge is also on the _Layout partial view:

<span class="badge" id="countCart"></span>
0
On

There's a server-side and client-side component here. First, a view component is only a thing server-side. Once it's rendered and the server returns the response to the client, that fact that it was a view component is entirely inconsequential. All it is client-side is a collection of nodes in the DOM.

Client-side, if you want to update just some portion of the page with new information from the server, you need to make an AJAX request. The JavaScript object behind this, XMLHttpRequest, is what you would call a "thin client". It simply makes the request and receives the response, but it does not do anything like rendering, adding stuff to the DOM, etc. on it's own. That is on your as the developer to determine how to handle the response from the server and what to do with it.

In that regard, you have a couple of options. First, you can have the server return a partial with some HTML. You cannot request or return your view component directly, so you'd need an action that returns PartialView, and a corresponding partial view that calls your view component:

public IActionResult GetCart() =>
    PartialView("_Cart");

Then, in _Cart.cshtml:

@await Component.InvokeAsync("Cart");

In the callback to your AJAX request, then, you'd select the existing "Cart" parent element on the page, and replace it with this new HTML returned from the server.

The second option is to simply have an endpoint that returns the current cart item count as JSON. For example:

{
    "items": 3
}

Then, in your AJAX callback, you can just replace the text of the badge with this number.