Create a gist now

Instantly share code, notes, and snippets.

Embed
JS: AngularJS Directive Attribute Binding Explanation

AngularJS Directive Attribute Binding Explanation

When using directives, you often need to pass parameters to the directive. This can be done in several ways. The first 3 can be used whether scope is true or false. This is still a WIP, so validate for yourself.

  1. Raw Attribute Strings

    <div my-directive="some string" another-param="another string"></div>
    
    var directiveFunction = function(){
    	return {
    		link: function(scope, element, attributes){
    
    			console.log(attributes.myDirective);
    			console.log(attributes.anotherParam);
    
    		}
    	};
    }
  2. Observing Interpolated Strings (Also Watches for Changes)

    <div my-directive="{{some string}}" another-param="another string"></div>
    
    var directiveFunction = function(){
    	return {
    		link: function(scope, element, attributes){
    		
    			console.log(attributes.myDirective); //literal string "{{some string}}", no interpolation
    			console.log(attributes.anotherParam); //literally "another string"
    
    			attributes.$observe('myDirective', function(value){
    				console.log(value);
    			});
    
    			attributes.$observe('anotherParam', function(value){
    				console.log(value);
    			});
    
    		}
    	};
    }
  3. Observing Expression Strings (Also watches for changes)

    <div my-directive="modelObject" another-param="modelObject.obj"></div>
    
    var directiveFunction = function(){
    	return {
    		link: function(scope, element, attributes){
    		
    			console.log(attributes.anotherParam); //literally "modelObject.obj"
    		
    			//modelObject is a scope property of the parent/current scope
    			scope.$watch(attributes.myDirective, function(value){
    				console.log(value);
    			});
    			
    			//modelObject.obj is also a scope property of the parent/current scope
    			scope.$watch(attributes.anotherParam, function (value){
    				console.log(value);
    			});
    			
    			//if you tried to use $observe, you would get the literal expression "modelObject" and "modelObject.obj", because there's nothing to interpolate
    
    		}
    	};
    }
  4. Evaluating Object Expressions

    <div my-directive="{ param: 34, param2: 'cool' }" another-param="another string"></div>
    
    var directiveFunction = function(){
    	return {
    		link: function(scope, element, attributes){
    
    			//this is designed for directive configuration if there's a alot of configuration parameters
    			//one can combine this with interpolation, if the configuration is a JSON string
    			var obj = scope.$eval(attributes.myDirective);
    			//can also fallback as a string
    			var string = scope.$eval(attributes.anotherParam);
    
    			console.log(obj);
    			console.log(string);
    
    		}
    	};
    }
  5. Isolate Scope One Way String Binding (Parent changes affect child, child changes does not affect parent)

    <div my-directive="{{some.string}}" another-param="{{another.string}}" string-param="somestring"></div>
    
    var directiveFunction = function(){
    	return {
    		scope: {
    			myDirective: '@',
    			anotherParam: '@',
    			stringParam: '@'
    		},
    		link: function(scope, element, attributes){
    
    			//the '@' binding automatically interpolates the "{{}}" if they exist in the attributes
    			console.log(scope.myDirective); //interpolated string
    			console.log(scope.anotherParam); //interpolated string
    			console.log(scope.stringParam); //literal string
    			
    			//IMPORTANT: if scope.some.string was not defined on the parent scope, then '@' interpolates it into an EMPTY string, it is still a STRING type
    			//if the DOM attribute was not defined, scope.property would returned undefined
    
    			//if the strings are not yet processed when this directive runs
    			//this means they are interpolated, but perhaps the true value has not arrived, such as content from AJAX
    			//see: http://stackoverflow.com/a/14907826/582917
    			attributes.$observe('myDirective', function(value){
    				if(value){
    					console.log(value);
    				}
    			});
    			attributes.$observe('anotherParam', function(value){
    				if(value){
    					console.log(value);
    				}
    			});
    
    			//the $watch method also works because the '@' binding already does the interpolation
    			//see: http://stackoverflow.com/a/12976630/582917 & http://stackoverflow.com/a/17224886/582917
    			//this only works because it's in an isolate scope, therefore myDirective is part of the scope
    			scope.$watch('myDirective', function(value){
    				if(value){
    					console.log(value);
    				}
    			});
    			
    			//the difference between $observe and $watch in this context, is that $observe reruns the interpolation (from the literal attribute value) and watches the value change, whereas the $watch relies on the '@' interpolation and parent binding and simply watches the value change in this current isolated scope
    			//this means if want to react to the value change coming from this directive, you'll need to use $watch, otherwise if you only want to react to parent changes, you may use $observe
    			
    			//multivalue value watching, only possible with $watch
    			//make sure to assign an object outside of the scope
    			//if you decide to return an object "return { }", a new object reference would be created
    			//inside the watch, and thus trigger another watch, resulting in digest errors
    			//MAKE SURE TO DO DEEP WATCH!
    			var multiValues = {};
    			scope.$watch(function(){
    				multiValues.myDirective = scope.myDirective;
    				multiValues.anotherParam = scope.anotherParam;
    				return multiValues;
    			}, function(value){
    				if(value){
    					console.log(value);
    				}
    			}, true);
    
    		}
    	};
    }
  6. Isolate Scope Two Way Object Binding (Parent changes affect child and child changes affect parent)

    <div my-directive="modelObject" another-param="{ thisWill: 'result in digest errors' }"></div>
    
    var directiveFunction = function(){
    	return {
    		scope: {
    			myDirective: '=', //use =? for optionality
    			anotherParam: '='
    		},
    		link: function(scope, element, attributes){
    
    			//modelObject needs to be defined on the parent scope or you can use "=?" for optionality
    			console.log(scope.myDirective); //this will be the parent/current scope's value, it's not the literal string
    			//this will result in digest errors: http://stackoverflow.com/q/13594732/582917
    			//so don't use object expressions with '='
    			console.log(scope.anotherParam);
    			
    			//IMPORTANT: if scope.modelObject was not defined on the parent scope, then '=' interpolates it into an UNDEFINED type, this works for child objects as well like scope.modelObject.childObject
    			//if the DOM attribute was not defined, scope.property would returned undefined
    
    			//the $watch method works when the model values have not yet been processed by the time this directive runs
    			scope.$watch('myDirective', function(value){
    				if(value){
    					console.log(value);
    				}
    			});
    			
    			//$observe is useless here, as there is no interpolation whatsoever, the $watch will react to changes from both parent scope and current scope, furthermore any change to the value here, changes the parent value as well
    			
    			//=? is required if you want to assign values to the current isolated scope property, but the DOM attribute or parent scope property was never defined, checking if it exists or logging its current value won't result in an error
    
    		}
    	};
    }
  7. Isolate Scope Object & Object Literal Expression Binding

    <div my-directive="{ param: 34, param2: 'cool' }" another-param="parentScopeObject"></div>
    
    var directiveFunction = function(){
    	return {
    		scope: {
    			myDirective: '&',
    			anotherParam: '&'
    		},
    		link: function(scope, element, attributes){
    
    			//this will return the actual object from the object expression!
    			console.log(scope.myDirective());
    			//this will return the actual object from the parent scope, if it exists of course!
    			//and no "parentScopeObject" is not a function, it's an object
    			console.log(scope.anotherParam());
    
    		}
    	};
    }
  8. Isolate Scope Function Expression Binding

    <div my-directive="parentScopeFunction(funcParam, secondParam)"></div>
    
    var directiveFunction = function(){
    	return {
    		template: '<button ng-click="myDirective({funcParam: 'blah blah', secondParam: 'blah blah'})">It can be executed from inside the DOM too!</button>',
    		scope: {
    			myDirective: '&'
    		},
    		link: function(scope, element, attributes){
    		
    			//IMPORTANT: if scope.parentScopeFunction was not defined on the parent scope, then '&' interpolates it into a NOOP function, it is still a FUNCTION type
    			//if the DOM attribute was not defined, scope.property would also still return a noop function
    
    			//if it's defined as something other than a function, an error occurs!
    			
    			//parameters passed into the bound function expression must be in the form of an object map
    			scope.myDirective(
    				{
    					funcParam: 'This is the value that is going to be passed in as the funcParam',
    					secondParam: 'This is another param!'
    				}
    			);
    
    		}
    	};
    }
  9. Non-Isolate Scope Function Expression Binding

    <div my-directive="parentScopeFunction(funcParam)" another-func="parentScopeFunction2()"></div>
    
    var directiveFunction = function($parse){
    	return {
    		link: function(scope, element, attributes){
    
    			//see this: 
    			//http://stackoverflow.com/questions/17583004/call-an-angularjs-controller-function-from-a-directive-without-isolated-scope
    			//http://stackoverflow.com/questions/14858682/angularjs-directive-with-isolate-scope-do-i-really-have-to-call-parent-everyw
    			
    			//apply and eval
    			scope.$apply(function() { 
    				scope.$eval(attributes.anotherFunc); 
    			});
    
    			//apply only
    			scope.$apply(attributes.anotherFunc);
    			
    			//$parse method, this allows parameters to be passed
    			var invoker = $parse(attributes.myDirective);
    			invoker(scope, {funcParam: 'example value'});
    
    		}
    	};
    }
@twig2let

This comment has been minimized.

Show comment
Hide comment
@twig2let

twig2let Jun 20, 2014

Great reference material, thanks!

Great reference material, thanks!

@stefanwalther

This comment has been minimized.

Show comment
Hide comment
@stefanwalther

stefanwalther Jun 27, 2014

Great collection! Thanks!

Great collection! Thanks!

@mjdenham

This comment has been minimized.

Show comment
Hide comment
@mjdenham

mjdenham Jul 9, 2014

Very helpful, especially 'parameters passed into the bound function expression must be in the form of an object map'.

mjdenham commented Jul 9, 2014

Very helpful, especially 'parameters passed into the bound function expression must be in the form of an object map'.

@AdityaJitu

This comment has been minimized.

Show comment
Hide comment
@AdityaJitu

AdityaJitu Oct 4, 2014

Very helpful collection . Thank you !

Very helpful collection . Thank you !

@JoachimR

This comment has been minimized.

Show comment
Hide comment
@JoachimR

JoachimR Oct 13, 2014

very clear and easy to read and understand. thank you very much

very clear and easy to read and understand. thank you very much

@Voxelot

This comment has been minimized.

Show comment
Hide comment
@Voxelot

Voxelot Oct 16, 2014

Extremely useful, thanks!

Voxelot commented Oct 16, 2014

Extremely useful, thanks!

@manojjsm

This comment has been minimized.

Show comment
Hide comment
@manojjsm

manojjsm Oct 29, 2014

Very helpful, Thank You ⭐️ ⭐️ ⭐️ ⭐️ ⭐️

Very helpful, Thank You ⭐️ ⭐️ ⭐️ ⭐️ ⭐️

@jamesbrobb

This comment has been minimized.

Show comment
Hide comment
@jamesbrobb

jamesbrobb Nov 6, 2014

Thanks for this.

Anyone know why i always get an error when attempting to call a function expression (No.9) with scope.$apply?

Error: [$rootScope:inprog] $digest already in progress

I've seen it used in loads of examples (for directives) and it's also done that way in the angular source. But i always get an error?

Thanks for this.

Anyone know why i always get an error when attempting to call a function expression (No.9) with scope.$apply?

Error: [$rootScope:inprog] $digest already in progress

I've seen it used in loads of examples (for directives) and it's also done that way in the angular source. But i always get an error?

@parsenz

This comment has been minimized.

Show comment
Hide comment
@parsenz

parsenz Nov 6, 2014

Incredibly helpful, thanks

parsenz commented Nov 6, 2014

Incredibly helpful, thanks

@helmi03

This comment has been minimized.

Show comment
Hide comment
@helmi03

helmi03 Nov 11, 2014

thanks, 5 stars

helmi03 commented Nov 11, 2014

thanks, 5 stars

@unr

This comment has been minimized.

Show comment
Hide comment
@unr

unr Nov 14, 2014

👍 You da bes.

unr commented Nov 14, 2014

👍 You da bes.

@karolisg

This comment has been minimized.

Show comment
Hide comment
@karolisg

karolisg Nov 23, 2014

Very helpful 👍

Very helpful 👍

@Wlada

This comment has been minimized.

Show comment
Hide comment
@Wlada

Wlada Feb 18, 2015

Thanks. This helps a lot!

Wlada commented Feb 18, 2015

Thanks. This helps a lot!

@gopisf

This comment has been minimized.

Show comment
Hide comment
@gopisf

gopisf Feb 23, 2015

Good examples for directive. Thank you.

gopisf commented Feb 23, 2015

Good examples for directive. Thank you.

@naveensf

This comment has been minimized.

Show comment
Hide comment
@naveensf

naveensf Feb 23, 2015

Best Examples for Directives

Best Examples for Directives

@kespeart

This comment has been minimized.

Show comment
Hide comment

kespeart commented Mar 6, 2015

Nice!!!!

@OliPelz

This comment has been minimized.

Show comment
Hide comment
@OliPelz

OliPelz Mar 17, 2015

This is so helpful! Thanks a lot!

OliPelz commented Mar 17, 2015

This is so helpful! Thanks a lot!

@dixon629

This comment has been minimized.

Show comment
Hide comment
@dixon629

dixon629 Mar 19, 2015

So helpful,thank u!

So helpful,thank u!

@imarkahann

This comment has been minimized.

Show comment
Hide comment

Thanks !

@sarunast

This comment has been minimized.

Show comment
Hide comment
@sarunast

sarunast Apr 15, 2015

The 3rd example doesn't work for me 😢

The 3rd example doesn't work for me 😢

@joshbuchea

This comment has been minimized.

Show comment
Hide comment
@joshbuchea

joshbuchea Apr 27, 2015

Very useful. Thanks!

Very useful. Thanks!

@AsuScholar

This comment has been minimized.

Show comment
Hide comment
@AsuScholar

AsuScholar May 15, 2015

This saved me soooo much time! Thanks for the great and detailed explanation!

This saved me soooo much time! Thanks for the great and detailed explanation!

@antivitla

This comment has been minimized.

Show comment
Hide comment
@antivitla

antivitla May 16, 2015

Thought i would never find this - big thanks to such work of collecting really tricky and tiny nuances of using bindings!

Thought i would never find this - big thanks to such work of collecting really tricky and tiny nuances of using bindings!

@martinmcwhorter

This comment has been minimized.

Show comment
Hide comment
@martinmcwhorter

martinmcwhorter May 20, 2015

This should be part of the angular 1.x documentation.

This should be part of the angular 1.x documentation.

@andresmgsl

This comment has been minimized.

Show comment
Hide comment
@andresmgsl

andresmgsl May 30, 2015

You really help me. Thanks

You really help me. Thanks

@DvirH

This comment has been minimized.

Show comment
Hide comment
@DvirH

DvirH Aug 26, 2015

Great very comprehensive!

DvirH commented Aug 26, 2015

Great very comprehensive!

@SimoneMSR

This comment has been minimized.

Show comment
Hide comment
@SimoneMSR

SimoneMSR Sep 18, 2015

This guide is very intriguing and complete. BTW, Example 3 does not watches for changes of existing modelObject, for me. Can you confirm, or instead better explain?

This guide is very intriguing and complete. BTW, Example 3 does not watches for changes of existing modelObject, for me. Can you confirm, or instead better explain?

@eventures-io

This comment has been minimized.

Show comment
Hide comment
@eventures-io

eventures-io Oct 2, 2015

Very clear. Thanks!

Very clear. Thanks!

@Jakobovski

This comment has been minimized.

Show comment
Hide comment

+10

@watcharaphong

This comment has been minimized.

Show comment
Hide comment
@watcharaphong

watcharaphong Nov 12, 2015

Very helpful Thank you

6 -> Isolate Scope Two Way Object Binding (Parent chnages -> change affect child and child changes affect parent)

Very helpful Thank you

6 -> Isolate Scope Two Way Object Binding (Parent chnages -> change affect child and child changes affect parent)

@mujuni88

This comment has been minimized.

Show comment
Hide comment
@mujuni88

mujuni88 Nov 17, 2015

Very helpful. Thank you a lot.

Very helpful. Thank you a lot.

@peterdouglas

This comment has been minimized.

Show comment
Hide comment
@peterdouglas

peterdouglas Nov 25, 2015

Great guide. Thank you!

Great guide. Thank you!

@laradevitt

This comment has been minimized.

Show comment
Hide comment
@laradevitt

laradevitt Nov 25, 2015

This was a great help. Thanks!

This was a great help. Thanks!

@Amitesh

This comment has been minimized.

Show comment
Hide comment
@Amitesh

Amitesh Dec 2, 2015

Awesome!!

Amitesh commented Dec 2, 2015

Awesome!!

@ulesta

This comment has been minimized.

Show comment
Hide comment
@ulesta

ulesta Dec 8, 2015

Awesome! Just what I needed! These should go up as examples in the actual docs!

ulesta commented Dec 8, 2015

Awesome! Just what I needed! These should go up as examples in the actual docs!

@secmax

This comment has been minimized.

Show comment
Hide comment
@secmax

secmax Dec 17, 2015

helpful, thanks

secmax commented Dec 17, 2015

helpful, thanks

@rootux

This comment has been minimized.

Show comment
Hide comment
@rootux

rootux Dec 31, 2015

Another interesting way is to use rootScope (Yes it's a bad habit).
you can define a function / variable on the rootscope and then access it.
If you are using a directive which has an isolated scope make sure you use $root like so: $scope.$root.myFunc()

rootux commented Dec 31, 2015

Another interesting way is to use rootScope (Yes it's a bad habit).
you can define a function / variable on the rootscope and then access it.
If you are using a directive which has an isolated scope make sure you use $root like so: $scope.$root.myFunc()

@qfish

This comment has been minimized.

Show comment
Hide comment
@qfish

qfish Jan 8, 2016

This was a great help. Thanks!

qfish commented Jan 8, 2016

This was a great help. Thanks!

@gabrielfeitosa

This comment has been minimized.

Show comment
Hide comment
@gabrielfeitosa

gabrielfeitosa Jan 27, 2016

Great material! Thanks for sharing

Great material! Thanks for sharing

@g3xx

This comment has been minimized.

Show comment
Hide comment
@g3xx

g3xx Feb 2, 2016

Thanks Om ! 👍

g3xx commented Feb 2, 2016

Thanks Om ! 👍

@aureustaurus

This comment has been minimized.

Show comment
Hide comment
@aureustaurus

aureustaurus Feb 4, 2016

Thanks! very helpful!

Thanks! very helpful!

@bastianwegge

This comment has been minimized.

Show comment
Hide comment
@bastianwegge

bastianwegge Feb 8, 2016

great stuff !

great stuff !

@taotau

This comment has been minimized.

Show comment
Hide comment
@taotau

taotau Feb 24, 2016

Angular binding in a nutshell. Thanks for the lightbulb moment!

taotau commented Feb 24, 2016

Angular binding in a nutshell. Thanks for the lightbulb moment!

@chacaldev

This comment has been minimized.

Show comment
Hide comment
@chacaldev

chacaldev Feb 26, 2016

Thanks!!! Really helpful!

Thanks!!! Really helpful!

@paladinwitzy

This comment has been minimized.

Show comment
Hide comment
@paladinwitzy

paladinwitzy Mar 24, 2016

Very helpful, thanks for sharing.

Very helpful, thanks for sharing.

@anilsingh581

This comment has been minimized.

Show comment
Hide comment
@anilsingh581

anilsingh581 Mar 26, 2016

It might help you for Attribute Binding in Angular
http://www.code-sample.com/2016/03/auto-capitalize-input-char-using.html
⭐️ ⭐️ ⭐️ ⭐️ ⭐️

It might help you for Attribute Binding in Angular
http://www.code-sample.com/2016/03/auto-capitalize-input-char-using.html
⭐️ ⭐️ ⭐️ ⭐️ ⭐️

@AndreyUtka

This comment has been minimized.

Show comment
Hide comment
@AndreyUtka

AndreyUtka Apr 7, 2016

What about one way expression bindings? ::item

What about one way expression bindings? ::item

@laranicolas

This comment has been minimized.

Show comment
Hide comment
@laranicolas

laranicolas Apr 7, 2016

Thanks for sharing!

Thanks for sharing!

@jamesjryan

This comment has been minimized.

Show comment
Hide comment
@jamesjryan

jamesjryan Apr 18, 2016

Sending more thanks your way.

Sending more thanks your way.

@Chima1707

This comment has been minimized.

Show comment
Hide comment
@Chima1707

Chima1707 Apr 23, 2016

Very helpful

Very helpful

@CMCDragonkai

This comment has been minimized.

Show comment
Hide comment
@CMCDragonkai

CMCDragonkai Jun 2, 2016

Thanks for all the feedback. This gist was written 2 years ago, and things may have changed in Angular in the mean time. Though a lot probably still applies. If any of you improve on it, feel free to fork it and put it up, or suggest a diff to apply to the gist above, and I'll update it, but I'm not able to verify whether any new changes works.

Owner

CMCDragonkai commented Jun 2, 2016

Thanks for all the feedback. This gist was written 2 years ago, and things may have changed in Angular in the mean time. Though a lot probably still applies. If any of you improve on it, feel free to fork it and put it up, or suggest a diff to apply to the gist above, and I'll update it, but I'm not able to verify whether any new changes works.

@CMCDragonkai

This comment has been minimized.

Show comment
Hide comment
@CMCDragonkai

CMCDragonkai Jun 14, 2016

By the way, I've worked on a project callee SnapSearch (https://snapsearch.io/) that allowd sites built in AngularJS and any Javascript framework to be indexed by search engines and social network robots. Might be useful to people frequenting this gist.

Owner

CMCDragonkai commented Jun 14, 2016

By the way, I've worked on a project callee SnapSearch (https://snapsearch.io/) that allowd sites built in AngularJS and any Javascript framework to be indexed by search engines and social network robots. Might be useful to people frequenting this gist.

@saucey

This comment has been minimized.

Show comment
Hide comment
@saucey

saucey Aug 6, 2016

good stuff

saucey commented Aug 6, 2016

good stuff

@gilderlan

This comment has been minimized.

Show comment
Hide comment

\o/

@AndrewIsh

This comment has been minimized.

Show comment
Hide comment
@AndrewIsh

AndrewIsh Sep 20, 2016

Excellent resource, thanks. Any chance you could break the comments? Having to scroll horizontally to read them is a PITA :)

Excellent resource, thanks. Any chance you could break the comments? Having to scroll horizontally to read them is a PITA :)

@abhishekbhalani

This comment has been minimized.

Show comment
Hide comment
@abhishekbhalani

abhishekbhalani Oct 22, 2016

Good stuff.. thank you 💯

Good stuff.. thank you 💯

@mdeggies

This comment has been minimized.

Show comment
Hide comment
@mdeggies

mdeggies Nov 6, 2016

Saved my butt. Thank you!

mdeggies commented Nov 6, 2016

Saved my butt. Thank you!

@NithinBiliya

This comment has been minimized.

Show comment
Hide comment
@NithinBiliya

NithinBiliya Dec 22, 2016

great material!

great material!

@bb-swapnil

This comment has been minimized.

Show comment
Hide comment
@bb-swapnil

bb-swapnil Mar 21, 2017

could not stop myself to say ' Its AWESOME" .... !!!!

could not stop myself to say ' Its AWESOME" .... !!!!

@sserbest

This comment has been minimized.

Show comment
Hide comment
@sserbest

sserbest Mar 28, 2017

Very helpful, thanks.

Very helpful, thanks.

@TSkills

This comment has been minimized.

Show comment
Hide comment
@TSkills

TSkills Apr 12, 2017

Nice nice nice !!

TSkills commented Apr 12, 2017

Nice nice nice !!

@wuxicn

This comment has been minimized.

Show comment
Hide comment
@wuxicn

wuxicn Apr 20, 2017

Awesome! Thank you!

wuxicn commented Apr 20, 2017

Awesome! Thank you!

@BurakAkyildiz

This comment has been minimized.

Show comment
Hide comment
@BurakAkyildiz

BurakAkyildiz Jun 14, 2017

Non isolated binding was helpfull thnx..

Non isolated binding was helpfull thnx..

@avrim

This comment has been minimized.

Show comment
Hide comment
@avrim

avrim Aug 4, 2017

HI,

In Example 2,
console.log(attributes.myDirective) WILL BE ITERPULATED

avrim commented Aug 4, 2017

HI,

In Example 2,
console.log(attributes.myDirective) WILL BE ITERPULATED

@TatyanaDubrova

This comment has been minimized.

Show comment
Hide comment
@TatyanaDubrova

TatyanaDubrova Nov 27, 2017

Cool explanation, thank you so much

Cool explanation, thank you so much

@fantasiiio

This comment has been minimized.

Show comment
Hide comment
@fantasiiio

fantasiiio Mar 1, 2018

Thank you very much !

Thank you very much !

@milad1367

This comment has been minimized.

Show comment
Hide comment
@milad1367

milad1367 May 22, 2018

Thanks, man!

Thanks, man!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment