JSFiddle - React, Tailwind, and code Playground

HTML

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.css">
<div ng-controller='myCtrl'>
     <h2>Demo</h3>
    <div>
        <div>
            <angular-star-rating max="maxValue" value='ratingValue'  hover='changeOnHover' is-readonly='isReadonly'></angular-star-rating>
        </div>
        <div>
            <p>Readonly:
                <input type='checkbox' ng-model='isReadonly' /> {{isReadonly}}
            </p>
            <p>Rating value change on mouse hover: 
                <input type='checkbox' ng-model='changeOnHover' /> {{changeOnHover}}
            </p>
            <p>Your rating value: {{ratingValue}} / {{maxValue}}</p>
            <p>Max value: 
                <input ng-model="maxValue" type="number" min="5" max="100"> (5~100 for test)
             </p>
        </div>
    </div>
</div>

SCSS

.fa-star-o {
    color: #FF7700;
}
.fa-star {
    color: #FFDD00;
    -webkit-text-stroke-width: 2px;
    -webkit-text-stroke-color: #FF7700;
}
.isReadonly {
    .fa-star-o {
        color: #000000;
    }
    .fa-star {
        color: #DCDCDC;
        -webkit-text-stroke-width: 2px;
        -webkit-text-stroke-color: #000000;
    }
}

JavaScript

angular.module('angular-star-rating', [])
    .directive('angularStarRating', angularStarRating);
var app = angular.module('myApp', ['angular-star-rating']);

function myCtrl($scope) {
    $scope.isReadonly = false; // default test value
    $scope.changeOnHover = false; // default test value 
    $scope.maxValue = 10; // default test value
    $scope.ratingValue = 5; // default test value
}


function angularStarRating() {
    var directive = {

        restrict: 'EA',
        scope: {
            'value': '=value',
                'max': '=max',
                'hover': '=hover',
                'isReadonly': '=isReadonly'
        },
        link: linkFunc,
        template:
            '<span ng-class="{isReadonly: isReadonly}">' +
            '<i ng-class="renderObj" ' +
            'ng-repeat="renderObj in renderAry" ' +
            'ng-click="setValue($index)" ' +
            'ng-mouseenter="changeValue($index, changeOnHover )" >' +
            '</i>' +
            '</span>',
        replace: true
    };
    return directive;
}

function linkFunc(scope, element, attrs, ctrl) {
    if (scope.max === undefined) scope.max = 5; // default
    console.log(scope.test);

    function renderValue() {
        scope.renderAry = [];
        for (var i = 0; i < scope.max; i++) {
            if (i < scope.value) {
                scope.renderAry.push({
                    'fa fa-star fa-2x': true
                });
            } else {
                scope.renderAry.push({
                    'fa fa-star-o fa-2x': true
                });
            }
        }
    }

    scope.setValue = function (index) {
        if (!scope.isReadonly && scope.isReadonly !== undefined) {
            scope.value = index + 1;
        }
    };

    scope.changeValue = function (index) {
        if (scope.hover) {
            scope.setValue(index);
        } else {
            // !scope.changeOnhover && scope.changeOnhover != undefined
        }
    };

    scope.$watch('value',...