<repo_name>GabrielCTroia/ionic-component-router<file_sep>/www/js/components/RootComponentAsDirective.js
const RootComponentAsDirective = () => ({
  //replace: 'true',
  template: `
    <ion-view>
      <h3>Root Component (as directive)</h3>

      <button ng-click="pushNextViewCtrl()">
        Component as View Controller
      </button>
      <br/>
      <button ng-click="pushNextDirective()">
        Component as Directive
      </button>
    </ion-view>
      `,
  scope: {
    nav: '=',
    params: '@',
  },
  controller: ['$scope', ($scope) => {
    console.log('root component as directive', $scope);

    const nextParams = {
      msg: 23
    };

    $scope.pushNextViewCtrl = $scope.nav.push.bind($scope.nav, ComponentAsViewController, nextParams);
    $scope.pushNextDirective = $scope.nav.push.bind($scope.nav, 'component-as-directive', nextParams);
  }]
});
<file_sep>/README.md
# ionic-component-router

**This is a really early conceptualization of some ideas I had as a solution to the shortcomings 
of ui-router in a mobile app.**

### Why
- Because ionic v2's navigation system is awesome but unfortunately it's also months away. 
Besides, ionic v1 apps that are not ready to migrate yet can make use this approach.
- I am planning to use it in my own Ionic v1 medium to large project

### What does it do?
- It mostly tries to follow what react-native, ios or Ionic v2 navigation looks like, but in Ionic & Angular 1.
- Eliminates the need to define routes/states at config time. There is no idea of route anymore,
 just what will the next screen show. Each screen shows a *component* at a time, which is represented by:
    - a directive that has a `<ion-view>...</ion-view>` template
    - a View Controller pair which is simply an object looking like: 
    ```
    {
       template: "<ion-view>...</ion-view>",
       controller: ['$scope', ($scope) => {...}]
    }
    ```

### To Do:    

- Ask more people if they think this can be useful!!! Please share your thoughts with me at [@gabrielctroia](https://twitter.com/gabrielctroia) on twitter.

- To make the migration easier from ui-router, it can support an already defined state
- Optimize the changes made to the DOM
- Cache a limited amount of viewed components
- Connect it to angular's animation system for transitions
- look into lazy loading the templates??

### How:

I promise I will update this as I move further, but for now please look at www/index.html and www/js/components<file_sep>/www/js/components/provider.js
angular.module('starter')

  .directive('app', [RootComponentAsDirective])
  .directive('componentAsDirective', [ComponentAsDirective])
<file_sep>/www/js/components/ComponentAsDirective.js
const ComponentAsDirective = () => ({
  //replace: 'true',
  template: `
    <ion-view>
      <button ng-click="nav.pop()">back</button>
      <h3>Component As Directive</h3>
      passed params: {{ params }}
      <br/>
      local vars: {{ local }}
      <br/>
      <br/>
      <button ng-click="pushNextViewCtrl()">
        Go To Component As View
      </button>
    </ion-view>
      `,
  scope: {
    nav: '=',
    params: '=',
  },
  controller: ['$scope', ($scope) => {
    $scope.local = 'just a local var';
    const nextParams = {
      msg: 'params from directive'
    };

    $scope.pushNextViewCtrl = $scope.nav.push.bind($scope.nav, ComponentAsViewController, nextParams);
  }]
});
<file_sep>/www/js/ComponentRouter.js
angular.module('ionic-component-router', [])

  .directive('navController', [
    '$compile', '$controller', '$interpolate',
    ($compile, $controller) => {
      return {
        //transclude: true,
        //replace: true,
        restrict: 'E',
        priority: 99999, // highest priority
        terminal: true, // needs to be terminal so the child directive will not render 2 times
        scope: {},
        compile(tElm) {
          let $rootElm = tElm.children();
          return {
            pre($scope, $elm) {
              const nav = new Nav($compile, $scope, $elm, angular, $controller);

              nav.push({root: $rootElm});
              nav.onChange((history) => {
                console.log('Current View', history.slice(-1)[0]);
                console.log('  history:', history);
              });

            }
          }
        }
      }
    }
  ]);

class Nav {
  constructor($compile, $scope, $wrapperElm, angular, $controller) {
    this._history = [];
    this._rendered = {};
    this._onChangeSubscribers = [];

    this._$parentScope = $scope;
    this._$wrapperElm = $wrapperElm;
    this._$compile = $compile;
    this._$controller = $controller;
    this._angular = angular;
  }

  push(viewController, params = {}) {
    if (typeof viewController === 'string') {
      console.log('is a registered directive view controller');
    } else {
      console.log('is an unregistered view ctrl');
    }

    this._history.push({viewController, params});
    this._publishOnChange();
  }

  pop() {
    this._history.pop();
    this._publishOnChange();
  }

  onChange(fn) {
    if (typeof fn === 'function') {
      this._onChangeSubscribers.push(fn);
    } else {
      console.warn('Nav.onChange(fn) must be a function!');
    }
  }

  _publishOnChange() {
    const last = this._history.slice(-1)[0];
    this._render(last.viewController, last.params);

    Array.prototype.map.call(this._onChangeSubscribers, f => f(this._history));
  }

  _compile({template, controller}, params) {
    const localScope = this._$parentScope.$new();
    localScope.nav = this;
    localScope.params = params;

    if (controller) {
      const c = this._$controller(controller, {
        $scope: localScope
      });
      template.data('$ngControllerController', c);
    }

    return this._$compile(template)(localScope);
  }

  _render(vc, params) {
    const nextId = 'vc-' + this.history.length;

    if (this._rendered[nextId]) {
      console.log("I'm already rendered", this._rendered);

      this._cleanDOM();
      document.getElementById(nextId).style.display = '';
      return this._rendered[nextId];
    }

    // normalize vc

    let template;
    // if directive
    if (typeof vc === 'string') {
      template = `<${vc} id="${nextId}" nav="nav" params="params"></${vc}>`;
      // if root
    } else if (vc.root) {
      template = vc.root;

      template.attr('id', nextId);
      template.attr('nav', 'nav');
      template.attr('params', 'params');

      // if view-controller pair
    } else {
      template = this._angular.element(vc.template);
    }

    //

    const compiled = this._compile({template, controller: vc.controller}, params);


    // append to the DOM only if the VC is not root
    if (this.history.length > 0) {
      //this._cleanDOM();
      this._$wrapperElm.append(compiled);
    }

    //this._rendered[nextId] = compiled;
  };

  _cleanDOM() {
    const lastID = 'vc-' + (this.history.length - 1);
    document.getElementById(lastID).style.display = 'none';
  }

  get history() {
    return this._history;
  }
}
