Here's how I refactored Navbar menu toggle logic in ngx-bulma.

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:

  1. The npm run build:lib:watch command watches and compiles any changes made to lib.
  2. The npm run start command starts bulma-app project which serves as documentation and also as testing ground for our changes in library.

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 call unsubscribe() from the nodes in ngOnDestroy 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!