Background:
I stumbled upon ngx-bulma project on GitHub. While browsing Issues page, I found this one which piqued my interest. The issue talks about refactoring their Navbar component implementation such that end users wouldn't need to have write boilerplate code for toggling navbar-menu
.
I felt this was a low hanging fruit and I should pick it up. Take a look at my commit for better understanding.
My journey began by forking the GitHub repo and cloning it to my home directory.
git clone https://github.com/shanmukhateja/ngx-bulma/
After running npm install
to let dependencies install, I opened the project directory in VS Code.
I feel this part isn't explained well in CONTRIBUTING.md
but to work on this project you need two terminal instances:
- The
npm run build:lib:watch
command watches and compiles any changes made to lib. - The
npm run start
command startsbulma-app
project which serves as documentation and also as testing ground for our changes in library.
Navbar component:
This component is located at projects/ngx-bulma/projects/ngx-bulma/src/lib/navbar/navbar.component.ts
. This component is a "holder" component as in the entire navbar resides as child nodes to it.
The issue takes place with ng-content
tag inside the component which holds the navbar-menu
and navbar-burger
div elements which we need.
So, we want to get a hold of the child elements of ng-content
. I had to research a bit but in the end I learned that it is possible with ContentChild.
From Angular docs:
Use to get the first element or the directive matching the selector from the content DOM.
In simpler terms, it fetches the first child element that matches the given selector input (string). So, in our case, we can fetch child navbar items inside ng-content
using ContentChild
.
@ContentChild(BulmaNavbarBrandComponent, { read: ElementRef }) cc: ElementRef; <-- #1
The above code returns an ElementRef
to bu-nav-brand
component which contains the required navbar-burger
as well as the navbar-menu
items which we're interested in.
Theory:
To make this work, we will need to get access to child nodes of BulmaNavbarComponent
that have navbar-burger
class to them. Next, we need access to nav-menu
items themselves to show/hide when navbar-burger
is clicked. These nodes are available within the same component so we inject ElementRef
in the constructor and querySelector
them within itself ( i.e. BulmaNavbarComponent
).
We can now either use addEventListener
or go fancier with fromEvent
from rxjs
to implement the actual toggle logic. (I chose the fancier route!).
Note: I could have called document.querySelector
and implemented the click functionality to all nodes in the entire page however that would mean my component would be manipulating nodes which are not it's children and it felt very wrong.
We should call
removeEventListener
or callunsubscribe()
from the nodes inngOnDestroy
when done to avoid memory leaks.
Implementation:
Now that we have a working theory, I went ahead with writing the code in projects/ngx-bulma/src/lib/navbar/navbar.component.ts
by creating two subscriptions which are initialized at ngAfterViewInit
to handle toggle event and unsubscribed at ngOnDestroy
when component is destroyed.
Additionally, I added a flag useBuiltInToggler
for BulmaNavbarComponent
to provide users to implement toggle events by themselves if they desire.
Conclusion:
It took me a month to get the functionality working. I was battling my imposter syndrome during first 2 weeks, assuring myself the code I wrote is up to standards and would be completely alright for me to send the PR. I finally pushed the code to my fork and asked Santhosh for a quick review for confirmation.
I want to say although it wasn't my first open source contribution, this PR always felt like one! It taught me great deal about read
option in ViewChild
and ContentChild
APIs as well as satisfaction that my code will help my fellow developers.
In this context, I highly encourage contributing to an open source project, it'll expand your understandings of the framework that you use. Cheers!