There are 3 main cases for an ideal solution to your problem:
- ViewComponent is added 0 times and the corresponding JavaScript library is added 0 times
- ViewComponent is added 1 time, the corresponding JavaScript library is added 1 time and the dynamically created initialization JavaScript is created once and placed after the library.
- ViewComponent is added many times, the corresponding JavaScript library is added 1 time and the dynamically created initialization JavaScript is created for each ViewComponent and are all placed after the library.
So in your example, jquery and app.js are your libraries while your dynamically created initialization JavaScript is the part that references @Model in your <script type="text/javascript">
tag. Let's say we added your component to a view 3 times (I'll replace @RenderBody() with the resulting html from a view that invokes your component 3 times):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MyProj.Web</title>
<environment names="Development,Staging,Production">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/lib/font-awesome/css/font-awesome.css" />
<link rel="stylesheet" href="~/css/site.css" />
<link rel="stylesheet" href="~/css/culture-flags.css" />
</environment>
</head>
<body>
<header>@Html.Partial("~/Views/Shared/_Header.cshtml")</header>
<main>
<nav></nav>
<article>
<div class="container body-content">
<!--@RenderBody()-->
<!--@await Component.InvokeAsync("MyComponent", new MyViewModel {cssClass="t1", ImageUrl="1.jpg"})-->
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
<script type="text/javascript">
var cssClass= "t1";
var imageUrl = "1.jpg";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
<!--@await Component.InvokeAsync("MyComponent", new MyViewModel {cssClass="t2", ImageUrl="2.jpg"}) -->
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
<script type="text/javascript">
var cssClass= "t2";
var imageUrl = "2.jpg";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
<!--@await Component.InvokeAsync("MyComponent", new MyViewModel {cssClass="t3", ImageUrl="3.jpg"}) -->
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/app.js" asp-append-version="true"></script>
<script type="text/javascript">
var cssClass= "t3";
var imageUrl = "t3.jpg";
webbCore.carouselSetBackgroundImage(cssClass, imageUrl);
</script>
</div>
</article>
<aside></aside>
</main>
<footer>@Html.Partial("~/Views/Shared/_Footer.cshtml")</footer>
<!-- These are the libraries that should only be loaded once -->
<environment names="Development,Staging,Production">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<!-- Ideally this is where the dynamically create scripts would go -->
@RenderSection("scripts", required: false)
</body>
</html>
As you can see, you would be loading the jquery library 4 times, once for your Layout and 3 times for your ViewComponents. Any good browser should only download the file once but will be loaded into memory multiple times and will just overwrite the the same global variables multiple times($ for example).
You might also be tempted to move the library to the top of the Layout and remove the references from the View Component but that is not a best practice either.
The main issue is that section
doesn't work with ViewComponents and that appears to be by design. You should think of a ViewComponent as a fancy html helper. I haven't seen any great solutions to this problem but here are a couple of ideas.
- Within the View, immediately after you call your component (Component.InvokeAsync("MyComponent")), add your javascript to @section scripts {...}
- Create a library js that initializes this component and set data attributes from the element
$(".carousel").each(function() {
var css = $(this).data("carousel-css");
var image = $(this).data("carousel-image");
});
<input class="carousel" type=hidden data-carousel-css="@Model.cssClass" data-carousel-image="@Model.imageURL" />