In this step, you will split the Home
component into individual components. The Home
component will be the placeholder page of your portfolio site. Each section will become a component like Header
, Hero
, AboutMe
, Portfolio
and Footer
.
OK. Let's get started!
If you haven't completed the previous step or want to start from the save point, run the following commands to restore the Blazor WebAssembly project.
cd $CODESPACE_VSCODE_FOLDER
mkdir -p workshop && cp -a save-points/step-05/. workshop/
cd workshop
-
Create a new file called
HeaderComponent.razor
in theMyPortfolio/Components
directory.mkdir -p $CODESPACE_VSCODE_FOLDER/workshop/MyPortfolio/Components touch MyPortfolio/Components/HeaderComponent.razor
-
Copy the following content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/HeaderComponent.razor
:<div id="header"> <a href="#home" target="_top">Home</a> <a href="#about" target="_top">About</a> <a href="#portfolio" target="_top">Portfolio</a> <a href="#contact" target="_top">Contact</a> </div>
-
Create a new file called
HeaderComponent.razor.css
in theMyPortfolio/Components
directory.touch MyPortfolio/Components/HeaderComponent.razor.css
-
Copy the following content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/HeaderComponent.razor.css
:#header { position: fixed; display: flex; justify-content: center; gap: 2rem; background: rgba(255,255,255,0.75); padding: 1rem; top: 0; width: 100%; z-index: 10; }
-
Create a new file called
HeroComponent.razor
in theMyPortfolio/Components
directory.touch MyPortfolio/Components/HeroComponent.razor
-
Add the following using statements to the top of the
MyPortfolio/Components/HeroComponent.razor
file:@using System.Net.Http.Json @using MyPortfolio.Models @inject HttpClient Http
-
Copy the following HTML content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/HeroComponent.razor
:@inject HttpClient Http @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ <section class="dark" id="home"> @if (heroHome is not null) { <img class="background" src="@(heroHome.Src)" alt="@(heroHome.Alt)" /> } <div style="position: absolute; top: 30%; left: 2rem;"> @if (property is null) { <p><em>Loading...</em></p> } else { <h1>@property.Name</h1> <h2>@property.Title</h2> } </div> <div style="position: absolute; bottom: 8rem; left: 50%;"> <a href="#about" target="_top"> <img src="images/down-arrow.svg" style="height: 3rem; width: 3rem;" alt="scroll down" /> </a> </div> </section>
-
Copy the following code block from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/HeroComponent.razor
:</section> @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ @code { private SiteProperties? property; private HeroImage? heroHome; protected override async Task OnInitializedAsync() { property = await Http.GetFromJsonAsync<SiteProperties>("sample-data/siteproperties.json"); var heros = await Http.GetFromJsonAsync<List<HeroImage>>("sample-data/heroimages.json"); heroHome = heros.SingleOrDefault(h => h.Name == "home"); } }
-
Create a new file called
AboutMeComponent.razor
in theMyPortfolio/Components
directory.touch MyPortfolio/Components/AboutMeComponent.razor
-
Add the following using statements to the top of the
MyPortfolio/Components/AboutMeComponent.razor
file:@using System.Net.Http.Json @using MyPortfolio.Models @inject HttpClient Http
-
Copy the following HTML content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/AboutMeComponent.razor
:@inject HttpClient Http @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ <section class="light" id="about"> @if (heroAboutMe is not null) { <img class="background" src="@(heroAboutMe.Src)" alt="@(heroAboutMe.Alt)" /> } <h2>About Myself</h2> <div style="background-color: white; width: 50%; padding: 4rem; margin: 3rem auto; text-align: center;"> @if (aboutMe is null) { <p><em>Loading...</em></p> } else { <p class="large">@aboutMe.Description</p> <hr /> <ul style="text-align: left; columns: 2; font-size: 1.25rem; margin: 2rem 3rem; gap: 3rem;"> @foreach (var skill in aboutMe.Skills) { <li key="@skill">@skill</li> } </ul> <hr /> <p style="padding: 1rem 3rem 0;">@aboutMe.DetailOrQuote</p> } </div> </section>
-
Copy the following code block from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/AboutMeComponent.razor
:</section> @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ @code { private AboutMe? aboutMe; private HeroImage? heroAboutMe; protected override async Task OnInitializedAsync() { aboutMe = await Http.GetFromJsonAsync<AboutMe>("sample-data/aboutme.json"); var heros = await Http.GetFromJsonAsync<List<HeroImage>>("sample-data/heroimages.json"); heroAboutMe = heros.SingleOrDefault(h => h.Name == "about"); } }
-
Create a new file called
PortfolioComponent.razor
in theMyPortfolio/Components
directory.touch MyPortfolio/Components/PortfolioComponent.razor
-
Add the following using statements to the top of the
MyPortfolio/Components/PortfolioComponent.razor
file:@using System.Net.Http.Json @using MyPortfolio.Models @inject HttpClient Http
-
Copy the following HTML content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/PortfolioComponent.razor
:@inject HttpClient Http @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ <section class="light" id="portfolio"> <h2>Portfolio</h2> <div class="portfolio-container"> @if (projects is null) { <p><em>Loading...</em></p> } else { <div class="portfolio-hero"> @if (heroPortfolio is not null) { <img src="@(heroPortfolio.Src)" style="height: 90%; width: 100%; object-fit: cover;" alt="@(heroPortfolio.Alt)" /> } </div> <div class="container"> @foreach (var project in projects) { <div class="box" key="@project.Title"> <a href="@project.Url" target="_blank" rel="noopener noreferrer"> <h3 style="flex-basis: 40px;">@project.Title</h3> </a> <p class="small">@project.Description</p> </div> } </div> } </div> </section>
-
Copy the following code block from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/PortfolioComponent.razor
:</section> @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ @code { private List<Project>? projects; private HeroImage? heroPortfolio; protected override async Task OnInitializedAsync() { projects = await Http.GetFromJsonAsync<List<Project>>("sample-data/projects.json"); var heros = await Http.GetFromJsonAsync<List<HeroImage>>("sample-data/heroimages.json"); heroPortfolio = heros.SingleOrDefault(h => h.Name == "portfolio"); } }
-
Create a new file called
FooterComponent.razor
in theMyPortfolio/Components
directory.touch MyPortfolio/Components/FooterComponent.razor
-
Add the following using statements to the top of the
MyPortfolio/Components/FooterComponent.razor
file:@using System.Net.Http.Json @using MyPortfolio.Models @inject HttpClient Http
-
Copy the following HTML content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/FooterComponent.razor
:@inject HttpClient Http @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ <div id="contact" style="background-color: #4E567E;"> @if (property is null) { <div style="display: flex; justify-content: center; gap: 2.5rem;"> <p><em>Loading...</em></p> </div> } else if (icons is not null) { <div style="display: flex; justify-content: center; gap: 2.5rem;"> @if (string.IsNullOrWhiteSpace(property.Email) is false) { <a href="mailto:@(property.Email)"> <img src="@icons.Email" alt="email" class="social-icon" /> </a> } @if (string.IsNullOrWhiteSpace(property.LinkedIn) is false) { <a href="https://linkedin.com/in/@(property.LinkedIn)" target="_blank" rel="noopener noreferrer"> <img src="@icons.LinkedIn" alt="LinkedIn" class="social-icon" /> </a> } @if (string.IsNullOrWhiteSpace(property.GitHub) is false) { <a href="https://github.com/@(property.GitHub)" target="_blank" rel="noopener noreferrer"> <img src="@icons.GitHub" alt="GitHub" class="social-icon" /> </a> } </div> <p class="small" style="margin-top: 0; color: white;">Created by @property.Name</p> } </div>
-
Copy the following code block from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/FooterComponent.razor
:</section> @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ @code { private SiteProperties? property; private SocialIcons? icons; protected override async Task OnInitializedAsync() { property = await Http.GetFromJsonAsync<SiteProperties>("sample-data/siteproperties.json"); icons = await Http.GetFromJsonAsync<SocialIcons>("sample-data/socialicons.json"); } }
-
Create a new file called
FooterComponent.razor.css
in theMyPortfolio/Components
directory.touch MyPortfolio/Components/FooterComponent.razor.css
-
Copy the following content from
MyPortfolio/Pages/Home.razor
toMyPortfolio/Components/FooterComponent.razor.css
:#contact { display: flex; flex-direction: column; align-items: center; gap: 2.5rem; padding: 5rem 0 3rem; width: 100vw; }
We've so far split all the sections into individual components. Now, we need to update the Home
component to replace the sections with the components.
-
Open
MyPortfolio/Pages/Home.razor
and replace the content with the following code:@page "/" @using MyPortfolio.Components <PageTitle>My Portfolio</PageTitle> <HeaderComponent /> <HeroComponent /> <AboutMeComponent /> <PortfolioComponent /> <FooterComponent />
-
Make sure you're in the
workshop
directory.cd $CODESPACE_VSCODE_FOLDER/workshop
-
Build your Blazor WebAssembly project to make sure everything is working fine.
dotnet build
-
Run the Blazor WebAssembly project to see the updated application on your web browser.
dotnet watch run --project MyPortfolio
-
The updated application is open, and should look like below.
-
Stop the running application by pressing
Ctrl + C
in the terminal.
You might have found that each component is using the same HttpClient
to fetch the JSON data. It's not efficient to fetch the same data multiple times. We can parameterise the components to pass the data from the Home
component.
-
Open
MyPortfolio/Components/HeroComponent.razor
-
Remove the
@inject HttpClient Http
directive from the top of the file so that it will be:@using System.Net.Http.Json @using MyPortfolio.Models <section class="dark" id="home">
-
Replace the code block with the following code. As you can see both
HttpClient
andHeroImages
are now parameters and accept values from their parent component,Home
.@code { private SiteProperties? property; private HeroImage? heroHome; [Parameter, EditorRequired] public HttpClient? Http { get; set; } = default!; [Parameter, EditorRequired] public List<HeroImage> HeroImages { get; set; } = default!; protected override async Task OnInitializedAsync() { property = await Http.GetFromJsonAsync<SiteProperties>("sample-data/siteproperties.json"); heroHome = HeroImages.SingleOrDefault(h => h.Name == "home"); } }
-
Open
MyPortfolio/Components/AboutMeComponent.razor
-
Remove the
@inject HttpClient Http
directive from the top of the file so that it will be:@using System.Net.Http.Json @using MyPortfolio.Models <section class="light" id="about">
-
Replace the code block with the following code. As you can see both
HttpClient
andHeroImages
are now parameters and accept values from their parent component,Home
.@code { private AboutMe? aboutMe; private HeroImage? heroAboutMe; [Parameter, EditorRequired] public HttpClient? Http { get; set; } = default!; [Parameter, EditorRequired] public List<HeroImage> HeroImages { get; set; } = default!; protected override async Task OnInitializedAsync() { aboutMe = await Http.GetFromJsonAsync<AboutMe>("sample-data/aboutme.json"); heroAboutMe = HeroImages.SingleOrDefault(h => h.Name == "about"); } }
-
Open
MyPortfolio/Components/PortfolioComponent.razor
-
Remove the
@inject HttpClient Http
directive from the top of the file so that it will be:@using System.Net.Http.Json @using MyPortfolio.Models <section class="light" id="portfolio">
-
Replace the code block with the following code. As you can see both
HttpClient
andHeroImages
are now parameters and accept values from their parent component,Home
.@code { private List<Project>? projects; private HeroImage? heroPortfolio; [Parameter, EditorRequired] public HttpClient? Http { get; set; } = default!; [Parameter, EditorRequired] public List<HeroImage> HeroImages { get; set; } = default!; protected override async Task OnInitializedAsync() { projects = await Http.GetFromJsonAsync<List<Project>>("sample-data/projects.json"); heroPortfolio = HeroImages.SingleOrDefault(h => h.Name == "portfolio"); } }
-
Open
MyPortfolio/Components/FooterComponent.razor
-
Remove the
@inject HttpClient Http
directive from the top of the file so that it will be:@using System.Net.Http.Json @using MyPortfolio.Models <div id="contact" style="background-color: #4E567E;">
-
Replace the code block with the following code. As you can see
HttpClient
is now parameters and accept values from their parent component,Home
.@code { private SiteProperties? property; private SocialIcons? icons; [Parameter, EditorRequired] public HttpClient? Http { get; set; } = default!; protected override async Task OnInitializedAsync() { property = await Http.GetFromJsonAsync<SiteProperties>("sample-data/siteproperties.json"); icons = await Http.GetFromJsonAsync<SocialIcons>("sample-data/socialicons.json"); } }
-
Let's make the footer component even more interactive by adding another parameter. Add the following parameter just under the
Http
parameter.public HttpClient? Http { get; set; } = default!; // ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ [Parameter, EditorRequired] public string? BackgroundColor { get; set; } = default!;
-
Then, change the
div
tag to use theBackgroundColor
parameter.@using MyPortfolio.Models @* ⬇️⬇️⬇️ Remove codes below ⬇️⬇️⬇️ *@ <div id="contact" style="background-color: #4E567E;"> @* ⬇️⬇️⬇️ Add codes below ⬇️⬇️⬇️ *@ <div id="contact" style="background-color: @BackgroundColor;">
Now, each component has been parameterised. We need to pass the data from the Home
component to each component.
-
Open
MyPortfolio/Pages/Home.razor
and replace the content with the following code. As you can see, we instantiate theHttpClient
andHeroImages
only once and pass them to each component.@page "/" @using System.Net.Http.Json @using MyPortfolio.Components @using MyPortfolio.Models @inject HttpClient Http <PageTitle>My Portfolio</PageTitle> @if (heroImages is null) { <p><em>Loading...</em></p> } else { <HeaderComponent /> <HeroComponent Http="@Http" HeroImages="@heroImages" /> <AboutMeComponent Http="@Http" HeroImages="@heroImages" /> <PortfolioComponent Http="@Http" HeroImages="@heroImages" /> <FooterComponent Http="@Http" BackgroundColor="#4E567E" /> } @code { private List<HeroImage>? heroImages; protected override async Task OnInitializedAsync() { heroImages = await Http.GetFromJsonAsync<List<HeroImage>>("sample-data/heroimages.json"); } }
-
You can even do further refactoring by passing
SiteProperties
,AboutMe
,List<Project>
andSocialIcons
to each component. I'll leave it to you as an exercise.
-
Make sure you're in the
workshop
directory.cd $CODESPACE_VSCODE_FOLDER/workshop
-
Build your Blazor WebAssembly project to make sure everything is working fine.
dotnet build
-
Run the Blazor WebAssembly project to see the updated application on your web browser.
dotnet watch run --project MyPortfolio
-
The updated application is open, and should look like below.
-
Stop the running application by pressing
Ctrl + C
in the terminal.
Congratulations! You've successfully split the Home
component into individual components and parameterised them. You've also learned how to pass data from the parent component to the child component. In the next step, you'll learn how to deploy the Blazor WebAssembly application to GitHub Pages using GitHub Actions.