Q:

Two way binding with URL get params and Form value (options and sliderbar)

Currently, I can fetch the GET params via $location.$$search.
However, I still have no idea how to do 2 way binding for the URL and FORM on the following case.
As the following demo picture, when user updates the FORM elements the corresponding URL should be: https://lazyair.co/en/user/quick_search/index#?from=TOKYO&to=TAIPEI&depart=2016/06/03~2016/06/06&return=2016/06/08~2016/06/11&chart_type=column&depart_slider=10:00~24:00

Demo page: https://lazyair.co/en/user/quick_search/index
Sliderbar directive JavaScript code
  'use strict';

  quick_search_app.directive('ionslider',function($timeout){

      var get_hour_minute, getHHMMformat, isDepartureAtInInterval;

      get_hour_minute = function(value) {
        var hours, minutes;
        hours = Math.floor(value / 60);
        minutes = value - (hours * 60);
        if (hours.length === 1) {
          hours = '0' + hours;
        }
        if (minutes.length === 1) {
          minutes = '0' + minutes;
        }
        return [hours, minutes];
      };

      getHHMMformat = function(values) {
        var hours, minutes;

              hours = values[0].toString();
              minutes = values[1].toString();
              if (hours.length === 1) {
                hours = '0' + hours;
              }
              if (minutes.length === 1) {
                minutes = '0' + minutes;
              }
              return hours + ':' + minutes;
            }
      isDepartureAtInInterval = function(departure_at, slider){
          var t = new Date(Date.parse(departure_at))
          var HHMM_in_minutes = t.getUTCHours()*60 + t.getMinutes();
          return slider.from <= HHMM_in_minutes && slider.to >= HHMM_in_minutes;
      }
      var updateFlighSeries = function(slider, flight_series) {
        $.each(flight_series, function() {
            var current_series = this;
            angular.forEach(current_series.data, function(value, key) {
                  if(isDepartureAtInInterval(value.departure_at, slider)){
                      this.visible = true ;
                  }else{
                      this.visible = false ;
                  }
              }, current_series);
        });
      }
      return{
          restrict:'AE',
          scope: false,
          controller: 'quick_search_ctrl',

          link:function(scope, element, attr, ctrl){
              $(element).ionRangeSlider({
                      hide_min_max: true,
                      keyboard: true,
                      min: 0,
                      max: 1440,
                      from: 0,
                      to: 1440,
                      type: 'double',
                      step: 30,
                      prefix: "",
                      chartConfig: element.attr("chart-config"),
                      grid: true,
                      prettify: function (value) {
                        return getHHMMformat(get_hour_minute(value));
                      },
                      onChange: function(slider) {
                          var _this = this;
                          updateFlighSeries(slider, scope[_this.chartConfig].series)
                          angular.forEach(scope.chart_names, function(chart_cfg_name){
                                scope.$apply(function () {
                                  scope.lowestFlights[chart_cfg_name]  = angular.copy(scope.filterLowestPrice(scope[chart_cfg_name]))
                                  console.log(scope.lowestFlights[chart_cfg_name])
                                });
                          }, scope)
                      }
              });
          }
      }
  });

HTML
<ui-select.selectpicker{:theme => "select2", "ng-disabled" => "disabled", "ng-model" => "from", :name => "from", :theme => "select2", "ng-change"=>"updateDeparture(from)", :style => "width: 200px;", :required => "" }
  <ui-select-match{ "ng-cloak"=>"", :placeholder => t("from") } {{$select.selected.t_name}}  {{$select.selected.name}}</ui>
</ui>
<ui-select.selectpicker{"ng-disabled" => "disabled", "ng-model" => "to", :name => "to", :theme => "select2", "ng-change"=>"updateArrival(to)", :style => "width: 200px;", :required => ""}
  <ui-select-match.selectpicker{"ng-cloak"=>"", :placeholder => t("to")}  {{$select.selected.t_name}} {{$select.selected.name}}</ui>
  <ui-select-choices{:repeat => "node in arrivals | filter: $select.search" }
    <span ng-bind-html="node.t_name | highlight: $select.search"></span>
    <span ng-bind-html="node.name | highlight: $select.search"></span>
  </ui>
</ui>

url params were cleared in $rootScope.Scope#$digest cycle

I put a breakpoint inside $locationChangeSuccess and found the url params were cleared in $rootScope.Scope#$digest cycle
app.run(function ($rootScope) {
    $rootScope.$on('$locationChangeSuccess', function () {
        debugger
        console.log('$locationChangeSuccess changed!', new Date());
    });
});

The 2-way binding not working on directive
The 2-way binding not working on directive,
Actually the 2-way binding works on View, but not working on URL params

DEMO page http://133.130.101.114:3000/en/user/quick_search/index
controller(register departChartName and show its value with input box)
  $scope.departChartName = "yoyoyo"
  urlBinder.bind($scope, "departChartName", "DPNAME")

slider directive
app.directive('ionslider',function($timeout){
    return{
        restrict:'AE',
        scope: false,
        link:function(scope, element, attr, ctrl){
            $(element).ionRangeSlider({
                    chartName: element.attr("chart-name"),
                    onChange: function(slider) {
                        scope[this.chartName] = slider.from+"~"+slider.to
                        scope.$apply();
                    }

            });
        }

    }
});

A:

You can create a service to do a two way binding to a URL parameter:
angular.module('app').service('urlBinder', ['$location', function($location) {
    this.bind = function(
        scope,         // angular scope
        varName,       // string : name of the variable on the scope to bind to
        urlParamName   // string : name of the url parameter to bind to
        ) {

        // when scope variable changes, update the URL
        var unhookUrlUpdater = scope.$watch(varName, function(newValue) {
            $location.search(urlParamName, newValue);
        });

        // when the URL changes, update the scope variable
        var unhookScopeUpdater = scope.$on('$locationChangeSuccess', function() {
            var value = $location.search()[urlParamName];

            if (!angular.equals(scope[varName], value)) {
                scope[varName] = value;
            }
        });

        // return a function that can be called to remove the bindings
        return function() {
            unhookUrlUpdater();
            unhookScopeUpdater();
        };
    };
}]);

You can also do the same thing with getter/setter function instead of varName if the things you are binding aren't on the scope:
angular.module('app').service('urlBinder', ['$location', function($location) {
    this.bind = function(scope, getter, setter, urlParamName) {
        var unhookUrlUpdater = scope.$watch(getter, function(newValue) {
            $location.search(urlParamName, newValue);
        });

        var unhookScopeUpdater = scope.$on('$locationChangeSuccess', function() {
            var value = $location.search()[urlParamName];

            if (!angular.equals(getter(), value)) {
                setter(value);
            }
        });

        return function() {
            unhookUrlUpdater();
            unhookScopeUpdater();
        };
    };
}]);

In your controller:
var someVariable;
urlBinder.bind(
    $scope,
    function() { return someVariable; },
    function(value) { someVariable = value; },
    'url-name');

