submitting a new directive to angular js

Submitting a New Directive to Angular

Click here for TL;DR

Recently I embarked on the task of submitting a new directive to angular js. I am involved in several open source projects and am familiar with the process of contributing but I wasn’t ready for the carefully contstructed contribution process to Angular. I thought I’d get out a quick post about some trouble I ran into and how I solved it.

**This is up to date for angular.js repo as of June 17th 2015**

Submitting A New Directive to Angular

First things first. Read the README.md file on the angular/angular.js repo and the contribution documentation. I will just run through a brief synopsis of that.

Getting Started

You will need to have node, npm, bower, and grunt installed on your machine. These are some basics that every developer should already have and be familiar with. To learn more click the links below:

NodeJS
NPM
Bower
Grunt Task Runner

Forking the Repository

It is standard practice when wanting to contribute to an open source project that you fork the project onto your personal GitHub account, make the changes, and submit a Pull Request. Angular is no different so we will do this before submitting a new directive to angular.

Building AnguarJS

Once you have forked the repo into your personal account you need to build AngularJS locally. The following is an excerpt from Angular’s Contribution Documentation:

# Clone your Github repository:
git clone "git@github.com:/angular.js.git"

# Go to the AngularJS directory:
cd angular.js

# Add the main AngularJS repository as an upstream remote to your repository:
git remote add upstream "https://github.com/angular/angular.js.git"

# Install node.js dependencies:
npm install

# Install bower components:
bower install

# Build AngularJS:
grunt package

It was here that I ran into my first issue. When running grunt package I was getting multiple errors. In the end I believe the cause of all my troubles was that I was on a Windows machine using a combination of Cygwin and CMD to try and run all the necessary commands. Some of my issues were caused by not running an elevated level of CMD (Run As Administrator) so npm install wasn’t functioning as expected. In the end I switched over to my Mac and everything worked fine first shot… I know this avoiding the problem rather than fixing it but it is on the developer to know their setup and machine to be able to work through these kinks so the path you take may be different.

Something that I found very useful was to understand the inner workings of the grunt package command. It was by studying this that I found the solution to most my problems.

Here is a brief run through of what is happening when we run grunt package

In your basic setups everything is usually contained in one file. For example you would never have to look outside the Gruntfile.js file to see what Grunt is doing. This is not the cast for most things in the Angular repo. Notice in the Gruntfile.js file this snippet of code:

1
grunt.loadTasks('lib/grunt');

Seeing this I followed that path to find a utils and plugins file that gave insight to some of the tasks specified in the package command.

Subtasks of grunt ‘package’ task

The first thing that happens before each task in Grunt is a fresh npm install. The following code is responsible for that:

1
2
3
if (!process.env.TRAVIS) {
  grunt.task.run('shell:npm-install');
}

Next the bower task is ran. The logic for this can be found in the ./lib/grunt/plugins.js file. This is doing a bower install

Next we have the clean task. This cleans out the build and tmp folders.

Next is the buildall task. This can be found in the ./lib/grunt/plugins.js file. This task runs all the subtasks of the build tasks found in Gruntfile.js. There are 14 subtasks all together which include building your angular.js file.

Next package runs minall. The logic for this is also found in the plugins.js file and like buildall it runs all the subtasks for the min task in the Gruntfile.js file

Next collect-errors is ran. The logic for this can be found in ./lib/grunt/utils.js and it does just what it sounds like it does…

Okay now package will run the docs task. The logic for this can be found in the plugins.js file, what it is doing is running the gulpfile.js found in the /docs directory. This task is run:

1
gulp.task('default', ['assets', 'doc-gen', 'build-app', 'jshint']);

You can use the same logic we have been using to track back the Grunt tasks to discover what these tasks are doing.

Next the copy task will run. This runs an i18n task. You can learn more about what that means here but basically this task is creating files for different regions around the world.

Next is the write task which is creating the version files for your build.

Lastly is the compress task. This creates the ZIP file of your project build located in the build folder.

Okay now that we actually know what is happening when we run grunt package we will be able to better deduce the work required by us to submitting a new directive to angular js.

Our New Directive

The directive I created (which was rejected 🙁 because they had decided as a team already in previous discussions to not include this directive in the CORE Angular code) was ngVisible and ngInvisible. Here is a brief description of what that directive does:

ngVisibleInvisible works a lot like ngShowHide except that rather than toggling the display property it toggles the visibility property. This is useful when you want to hide an element but preserve the space it takes up in the DOM.

Because my directive is very similar to ngShow and ngHide I decided to mimic the code I found for ngShowHide in the angular.js repo.

Adding Directive’s Code

When searching through the angular.js repo I found ./src/ng/directive/ngShowHide.js. I created a new file in the directory called ngVisibleInvisible.js. The ngShowHide.js file is over 300 lines long while the actual JS logic is about 30 lines of code. The reason for this is because all of the code/text for documentation, protractor testing, and examples are also found in this file.

The meat of the ngShowHide directive is simply adding the two classes .ng-hide and .ng-hide-animate. When the ng-show or ng-hide attribute on the directive changes the animation class is temporarily added and then the hide/show class is either added or removed. Simple.

I went through and customized the javascript, documentation, examples, and tests for my visible and invisible logic and saved my file.

Also noticing that it was a CSS class that was being added and removed I added the following snippet to css/angular.css

1
2
3
.ng-invisible:not(.ng-invisible-animate) {
    visibility: hidden !important;
}

Re-Building angular.js

Now that I created my new directive I wanted to rebuild my environment and test it out! So.. lets run grunt package. I ran grunt package, followed the instructions on launching a webserver, and navigated to my directives in the documentation.

First thing I saw was what all that commented out code did. The documentation was updated with my new text and the examples and code source were updated with my new text. The next thing I saw was that my directive wasn’t working…

Tracking the problem

Now to find out why. First thing I did was check if the directive was being added to the example code. It was, I saw this upon inspection:

1
2
3
<div ng-invisible="checked">
...
</div>

Next I opened the angular.js file that the documentation was using. While searching through here I noticed that my directive code was missing. So I went to track down why my build file didn’t include my new work.

I noticed that the source for the build/angular.js was pulling from files['angularSrc'] which I tracked down to a angularFiles.js in the repo root. Opening this file I saw that each directive path was manually listed, this suprised me that the task wasn’t dynamically pulling js files into the build process, but none the less I added my directive files here.

1
2
3
4
5
6
7
8
9
10
...
    'src/ng/directive/ngShowHide.js',
    'src/ng/directive/ngStyle.js',
    'src/ng/directive/ngSwitch.js',
    'src/ng/directive/ngTransclude.js',
    'src/ng/directive/ngVisibleInvisible.js',
    'src/ng/directive/script.js',
    'src/ng/directive/select.js',
    'src/ng/directive/style.js',
...

I rebuilt the project and fired up my webserver, only to find that my code still wasn’t working… I got stuck here and decided to inspect the angular.js for all mentions of ngShow to see where else I needed to add ngVisible logic. I found this bit of code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
            ngInclude: ngIncludeDirective,
            ngInit: ngInitDirective,
            ngNonBindable: ngNonBindableDirective,
            ngPluralize: ngPluralizeDirective,
            ngRepeat: ngRepeatDirective,
            ngShow: ngShowDirective,
            ngStyle: ngStyleDirective,
            ngSwitch: ngSwitchDirective,
            ngSwitchWhen: ngSwitchWhenDirective,
            ngSwitchDefault: ngSwitchDefaultDirective,
            ngOptions: ngOptionsDirective,
            ngTransclude: ngTranscludeDirective,
...

When searching the repo for this chunk of text I found it in src/AngularPublic.js and added my directives to it:

1
2
3
4
...
            ngInvisible: ngInvisibleDirective,
            ngVisible: ngVisibleDirective,
...

**Note: After more trial and error I discovered I had to also add the code snippets to the commented out portion at the top of the page**

Now when i run grunt package and launch my webserver I can see that my new directive is working!!

Testing Your New Directive

I am making the assumption that you have some basic understanding of testing Angular with Karma. If not you can learn more about that with Lukas Ruebbelke’s guide.

The test file for ngShowHide was located in test/ng/directives/ngShowHideSpec.js. So this is where I stuck my ngVisibleInvisible unit test. I had to create a new helper function toBeVisible() which I stuck in the test/helpers/matcher.js file.

To run my new unit tests i used the following command:

1
grunt test:unit

Submitting A New Directive To Angular

The team at Angular request that you run a check before committing. To do this run:

1
grunt test

This is going to run all the linting, compilation, unit tests, etc. along with e2e tests. This is the first time we have ran e2e tests so you will found out now how your protractor tests do. In my case I had to make a slight change by checking for a css property rather than visibility due to this clause.

Once you are passing all your tests you can go ahead and create a pull request. Here are the recommended steps:

1
2
3
4
 $ git checkout -b my-fix-branch master
 $ git commit -a
 $ grunt test
 $ git push origin my-fix-branch

Now log into GitHub and submit a PR. Again please read the CONTRIBUTING.md doc to learn about how to write proper commit messages, etc..

I hope this was helpful to you and makes it a little bit easier for the community to get involved and contribute to Angular JS!

If you want to see the files I committed to the angular.js repo you can click here:

commit ….41d1c1d

If you are interested in using the ngVisibleInvisible directive you can find it here:

ngVisibleInvisible

In The End

Submitting a new directive to Angular

  • Clone the angular.js repo and install it’s dependencies
  • Install dependencies
  • Learn the grunt package command and all that it does to allow for debugging when adding your new files
  • Find an existing directive and use that as a template to create your new directive
  • Read the contribution guidelines so you are ready to submit a PR
  • Write tests before submitting a new directive to angular and test with grunt test
  • Submit a Pull Request!

Pro Tip: Determining visibility

Before March of 2013 for something to not be visible it could have display: none, opactiy:0, or visibility: hidden. New standards say that an element with a width or height is visible (along with other metrics). This means “isDisplayed()” like tasks in test won’t work for transparent and visibility hidden elements, you need custom logic.

See this documentation on W3 WebDriver Standards

One thought on “Submitting a New Directive to Angular

  1. PixnBits

    Instead of cygwin, you might try running the grunt tasks in the git bash shell. Not quite Linux, but close enough for most of my daily tasks.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *