<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"/>
    
    <script src="https://code.angularjs.org/1.3.0/angular.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>

    <script src="lib/app.js"></script> 
    <script src="observerLib/router-observer.js"></script>
    </head>

  <body ng-app="miApp">
    <div ng-controller="appController">
      
      <header class="well">
        <h1>Mi Sitio AngularJS: {{title}}</h1>
        <div class="btn-group">
          <a class="btn btn-primary" ui-sref="inicio">Inicio</a>
          <a class="btn btn-primary" ui-sref="productos">Productos</a>
          <a class="btn btn-primary" ui-sref="contacto">Contacto</a>
        </div>
      </header>

      <div class="container">
        <div ui-view></div>
      </div>

      <hr>
      <footer>
        <p>© 2025 - Layout Persistente</p>
      </footer>
      
    </div>
  </body>
</html>
window.layout = angular.module("layout", ["ui.router"]);

window.layout.config([
  "$stateProvider",
  "$urlRouterProvider",
  "$provide",
  function($stateProvider, $urlRouterProvider, $provide) {

    $provide.factory("$stateProvider", function () {
      return $stateProvider;
    });

    $provide.factory("$urlRouterProvider", function () {
      return $urlRouterProvider;
    });
}]);

window.layout.service('RouteMonitorService', ['$rootScope', function($rootScope) {
    
    var arrancado = false;

    this.iniciar = function(callback) {
        if (arrancado) return; 
        RouteObserverLib.getInstance().init(
            new AngularJsFactory($rootScope), 
            callback
        );

        arrancado = true;
    };
}]);

window.layout.run([
  "$stateProvider",
  "$urlRouterProvider",
  "$rootScope",
  "RouteMonitorService",
  function($stateProvider, $urlRouterProvider, $rootScope, RouteMonitorService) { 
    $urlRouterProvider.otherwise("/inicio");

    $stateProvider
      .state('inicio', {
        url: "/inicio",
        templateUrl: "lib/inicio.html" 
      })
      .state('productos', {
        url: "/productos",
        templateUrl: "lib/productos.html"
      })
      .state('contacto', {
        url: "/contacto",
        templateUrl: "lib/contacto.html"
      });

    RouteMonitorService.iniciar(function(data) {
      console.log(`*** Servicio Monitor: Ruta -> ${data.name}`);
      $rootScope.rutaActual = data.name;
    })
  }]);

var app = angular.module('miApp', ['layout']);

app.controller('appController', ['$scope', function($scope) {
  $scope.title = "Routing Dinámico";
}]);
<div>
  <h2>Contáctanos</h2>
  <p>Escríbenos a: <strong>AlgunEmail@hotmail.com</strong></p>
  <button ng-click="alert('Enviado!')">Enviar Mensaje</button>
</div>
<div>
  <h2>{{titulo}}</h2>
  <p>Esta es la página principal. El header de arriba no ha cambiado.</p>
</div>
<div>
  <h2>Catálogo de Productos</h2>
  <ul>
    <li ng-repeat="item in lista">{{item}}</li>
  </ul>
</div>
[ng\:cloak],
[ng-cloak],
[data-ng-cloak],
[x-ng-cloak],
.ng-cloak,
.x-ng-cloak {
  display: none !important;
}

h1,
p {
  font-family: sans-serif;
}

/* Estilos del Layout Fijo */
.header-fijo {
  background-color: #2c3e50;
  color: white;
  padding: 20px;
  text-align: center;
}

.header-fijo a {
  color: #ecf0f1;
  text-decoration: none;
  font-size: 18px;
  margin: 0 10px;
}

.footer-fijo {
  background-color: #95a5a6;
  padding: 10px;
  text-align: center;
  position: fixed;
  bottom: 0;
  width: 100%;
}

/* Estilos del Contenido Dinámico (ng-view) */
.contenido-dinamico {
  padding: 40px;
  background-color: #ecf0f1;
  min-height: 300px;
  border: 2px dashed #bdc3c7; /* Borde para que veas dónde se inyecta la vista */
  margin: 20px;
}
class RouteObserverLib {
  constructor() {
    this.listener = null;
  }

  static getInstance() {
    if (!RouteObserverLib.instance) {
      RouteObserverLib.instance = new RouteObserverLib();
    }
    return RouteObserverLib.instance;
  }

  init(factory, callback) {
    if (this.listener) {
      this.listener.stopListening();
    }

    this.listener = factory.createListener();
    
    this.listener.startListening(callback);
    console.log('%c RouteObserverLib: Inicializado correctamente.', 'color: green; font-weight: bold;');
  }
}

// Implementación concreta del Listener para AngularJS
class AngularJSListener {
  constructor($rootScope) {
    this.$rootScope = $rootScope;
    this.unsubscribe = null;
  }

  startListening(callback) {
    // Nos suscribimos al evento nativo de UI-Router
    this.unsubscribe = this.$rootScope.$on(
      '$stateChangeSuccess',
      (event, toState, toParams) => {
        const routeData = {
          name: toState.name,
          params: toParams,
          path: toState.url
        };
        callback(routeData);
      }
    );
  }

  stopListening() {
    if (this.unsubscribe) {
      this.unsubscribe();
      console.log('Listener detenido');
    }
  }
}

class AngularJsFactory {
  constructor($rootScope) {
    this.$rootScope = $rootScope;
  }

  createListener() {
    return new AngularJSListener(this.$rootScope);
  }
}