Cómo manejar enlaces de hash de anclaje en AngularJS


318

¿Alguno de ustedes sabe cómo manejar bien el enlace hash de anclaje en AngularJS ?

Tengo el siguiente marcado para una simple página de preguntas frecuentes

<a href="#faq-1">Question 1</a>
<a href="#faq-2">Question 2</a>
<a href="#faq-3">Question 3</a>

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="fa1-3">Question 3</h3>

Al hacer clic en cualquiera de los enlaces anteriores, AngularJS me intercepta y me dirige a una página completamente diferente (en mi caso, una página 404 ya que no hay rutas que coincidan con los enlaces).

Mi primer pensamiento fue crear una ruta que coincida con " / faq /: chapter " y en el controlador correspondiente verificar $routeParams.chapterdespués de un elemento coincidente y luego usar jQuery para desplazarnos hacia abajo.

Pero luego AngularJS me vuelve a cagar y de todos modos solo se desplaza a la parte superior de la página.

Entonces, ¿alguien aquí hizo algo similar en el pasado y conoce una buena solución?

Editar: Cambiar a html5Mode debería resolver mis problemas, pero de todos modos tenemos que admitir IE8 +, así que me temo que no es una solución aceptada: /


Creo que angular sugiere usar ng-href=""en su lugar.
superpuesto

10
Creo que ng-href solo es aplicable si la url contiene datos dinámicos que deben estar vinculados a un modelo ng. Me pregunto si asigna un prefijo hash al proveedor de ubicación si ignorará los enlaces a los ID: docs.angularjs.org/guide/dev_guide.services.$location
Adam

Adam tiene razón en el uso de ng-href.
Rasmus

Posible duplicado de enlaces
Michaël Benjamin Saerens

Este también es un problema para el nuevo Angular: stackoverflow.com/questions/36101756/…
phil294

Respuestas:


375

Usted está buscando $anchorScroll().

Aquí está la documentación (horrible).

Y aquí está la fuente.

Básicamente solo lo inyecta y lo llama en su controlador, y lo desplazará a cualquier elemento con la identificación que se encuentra en $location.hash()

app.controller('TestCtrl', function($scope, $location, $anchorScroll) {
   $scope.scrollTo = function(id) {
      $location.hash(id);
      $anchorScroll();
   }
});

<a ng-click="scrollTo('foo')">Foo</a>

<div id="foo">Here you are</div>

Aquí hay un saqueador para demostrar

EDITAR: para usar esto con enrutamiento

Configure su enrutamiento angular como de costumbre, luego simplemente agregue el siguiente código.

app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
  //when the route is changed scroll to the proper element.
  $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
    $location.hash($routeParams.scrollTo);
    $anchorScroll();  
  });
});

y tu enlace se vería así:

<a href="#/test?scrollTo=foo">Test/Foo</a>

Aquí hay un Plunker que muestra el desplazamiento con enrutamiento y $ anchorScroll

Y aún más simple:

app.run(function($rootScope, $location, $anchorScroll) {
  //when the route is changed scroll to the proper element.
  $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
    if($location.hash()) $anchorScroll();  
  });
});

y tu enlace se vería así:

<a href="#/test#foo">Test/Foo</a>

55
El problema puede surgir cuando agrega enrutamiento: si agrega ngVer cada cambio del hash de url desencadenaría la recarga de la ruta ... En su ejemplo, no hay enrutamiento y la url no refleja el elemento actual ... Pero gracias por señalar $ anchorScroll
Valentyn Shybanov

1
@blesh, llamar a location.hash (X) cambia la página ya que el enrutamiento controla las vistas.
dsldsl

24
Esta solución hace que toda mi aplicación se vuelva a procesar.

2
@dsldsl && @OliverJosephAsh: $ location.hash () no volverá a cargar la página. Si es así, está sucediendo algo más. Aquí está el mismo golpe con el tiempo que se escribe a medida que se carga la página, verá que no cambia. Si desea desplazarse a una etiqueta de anclaje en la página actual sin volver a cargar la ruta, simplemente haría un de manera regular <a href="#foo">foo</a>. Mi ejemplo de código era mostrar el desplazamiento a una identificación en routechange.
Ben Lesh

44
@blesh: Recomiendo que también elimine el hash después de desplazarse a la sección deseada, de esta manera la URL no está contaminada con cosas que realmente no deberían estar allí. Use esto: $location.search('scrollTo', null)
kumarharsh

168

En mi caso, noté que la lógica de enrutamiento estaba funcionando si modificaba el $location.hash(). El siguiente truco funcionó ...

$scope.scrollTo = function(id) {
    var old = $location.hash();
    $location.hash(id);
    $anchorScroll();
    //reset to old to keep any additional routing logic from kicking in
    $location.hash(old);
};

55
Gracias por eso, la lógica de enrutamiento se negó absolutamente a comportarse incluso cuando se usa la solución .run (...) de @ blesh, y esto lo ordena.
ljs

8
Su truco de "salvar el viejo hash" ha sido un salvavidas absoluto. Evita que la página se vuelva a cargar mientras mantiene limpia la ruta. ¡Idea increíble!
ThisLanham

2
Buena esa. Pero después de implementar su solución, la url no está actualizando el valor de la identificación de destino.
Mohamed Hussain

1
Tuve la misma experiencia que Mohamed ... De hecho, detuvo la recarga pero muestra la ruta sin hash (y $ anchorScroll no tuvo ningún efecto). 1.2.6 Hmmmm.
michael

3
Yo uso $location.hash(my_id); $anchorScroll; $location.hash(null). Impide la recarga y no tengo que administrar la oldvariable.
MFB

53

No es necesario cambiar ningún enrutamiento ni nada más, solo debe usarse target="_self"al crear los enlaces

Ejemplo:

<a href="#faq-1" target="_self">Question 1</a>
<a href="#faq-2" target="_self">Question 2</a>
<a href="#faq-3" target="_self">Question 3</a>

Y use el idatributo en sus elementos html como este:

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>

No es necesario usar ## como se señala / menciona en los comentarios ;-)


1
Esto no funcionó para mí, pero la solución aquí sí: stackoverflow.com/a/19367249/633107
Splaktar

66
Gracias por esta solución Pero target = "_ self" es suficiente. No es necesario duplicar #
Christophe P

55
target = "_ self" es definitivamente la mejor respuesta (no es necesario duplicar #, como señaló Christophe P). Esto funciona sin importar si Html5Mode está activado o desactivado.
newman

3
Simple y completo. Me funcionó sin necesidad de agregar otra dependencia angular.
adeady

44
Esta es la solución correcta. No es necesario involucrar el servicio angular $ anchorScroll. Consulte la documentación de una etiqueta: https://developer.mozilla.org/en/docs/Web/HTML/Element/a
Yiling

41
<a href="##faq-1">Question 1</a>
<a href="##faq-2">Question 2</a>
<a href="##faq-3">Question 3</a>

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>

¡Increíble! Con mucho, la solución más simple, pero ¿alguna idea de cómo vincular un ancla en una página separada? (es decir, / productos # libros)
8bithero

Creo que es lo mismo que la solución (/ productos ## libros) en AngularJS
lincolnge

1
Desde mi experiencia, href = "##" funciona solo cuando se inyecta $ anchorScroll.
Brian Park

99
esto parece relativamente simple pero no funciona :-(
brykneval

44
Agregué target = "_ self" y funcionó de maravilla para todo tipo de navegación dentro de la página (leer controles deslizantes, ir a diferentes secciones, etc.). Gracias por compartir este gran y simple truco.
Kumar Nitesh

19

Si siempre conoce la ruta, simplemente puede agregar el ancla de esta manera:

href="#/route#anchorID

donde routees la ruta angular actual y anchorIDcoincide con un <a id="anchorID">lugar en la página


1
Esto desencadena un cambio de ruta normal de AngularJS y, por lo tanto, no se recomienda. En mi caso, fue muy visual ya que los videos de YouTube en la página de Preguntas frecuentes / Ayuda se volvieron a cargar.
Robin Andersson

99
@ RobinWassén-Andersson al especificar reloadOnSearch: falseesa ruta en la configuración de rutas, angular no activará un cambio de ruta y simplemente se desplazará a la identificación. En combinación con la ruta completa especificada en la aetiqueta, diría que esta es la solución más simple y directa.
Elise

Gracias. Esto me ayudo. No uso ninguna ruta personalizada en mi aplicación, por lo que hacer un href = "# / # anchor-name" funcionó muy bien.
Jordan

14

$anchorScroll funciona para esto, pero hay una forma mucho mejor de usarlo en versiones más recientes de Angular.

Ahora, $anchorScrollacepta el hash como argumento opcional, para que no tenga que cambiar $location.hashen absoluto. ( documentación )

Esta es la mejor solución porque no afecta la ruta en absoluto. No pude hacer funcionar ninguna de las otras soluciones porque estoy usando ngRoute y la ruta se volvería a cargar tan pronto como la configurara $location.hash(id), antes $anchorScrollpodría hacer su magia.

Aquí está cómo usarlo ... primero, en la directiva o controlador:

$scope.scrollTo = function (id) {
  $anchorScroll(id);  
}

y luego en la vista:

<a href="" ng-click="scrollTo(id)">Text</a>

Además, si necesita tener en cuenta una barra de navegación fija (u otra IU), puede establecer el desplazamiento para $ anchorScroll de esta manera (en la función de ejecución del módulo principal):

.run(function ($anchorScroll) {
   //this will make anchorScroll scroll to the div minus 50px
   $anchorScroll.yOffset = 50;
});

Gracias. ¿Cómo implementaría su estrategia para un enlace hash combinado con un cambio de ruta? Ejemplo: haga clic en este elemento de navegación, que abre una vista diferente y se desplaza hacia abajo a un punto específico iden esa vista.
Kyle Vassella

Sry para molestar ... Si tienes la oportunidad, ¿podrías echar un vistazo a mi pregunta de Stack? Siento que su respuesta aquí me ha acercado mucho, pero no puedo implementarla: stackoverflow.com/questions/41494330/… . Yo también estoy usando ngRoutey la versión más nueva de Angular.
Kyle Vassella

Lo siento, no he probado ese caso en particular ... pero ¿has echado un vistazo a $ location.search () o $ routeParams ? Quizás podría usar uno u otro en la inicialización de su controlador: si su parámetro scrollTo search está en la URL, entonces el controlador podría usar $ anchorScroll como arriba para desplazar la página.
Rebecca

2
Al pasar el Id directamente a $ anchorScroll, mis rutas cambiaron de algo como / contact # contact a just / contact. Esta debería ser la respuesta aceptada en mi humilde opinión.
RandomUs1r

12

Esta fue mi solución usando una directiva que parece más Angular-y porque estamos tratando con el DOM:

Por aquí

github

CÓDIGO

angular.module('app', [])
.directive('scrollTo', function ($location, $anchorScroll) {
  return function(scope, element, attrs) {

    element.bind('click', function(event) {
        event.stopPropagation();
        var off = scope.$on('$locationChangeStart', function(ev) {
            off();
            ev.preventDefault();
        });
        var location = attrs.scrollTo;
        $location.hash(location);
        $anchorScroll();
    });

  };
});

HTML

<ul>
  <li><a href="" scroll-to="section1">Section 1</a></li>
  <li><a href="" scroll-to="section2">Section 2</a></li>
</ul>

<h1 id="section1">Hi, I'm section 1</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. 
 Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. 
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium. 
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>

<h1 id="section2">I'm totally section 2</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. 
 Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. 
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium. 
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>

Usé el servicio $ anchorScroll. Para contrarrestar la actualización de la página que acompaña al cambio de hash, seguí adelante y cancelé el evento locationChangeStart. Esto funcionó para mí porque tenía una página de ayuda conectada a un ng-switch y las actualizaciones esencialmente romperían la aplicación.


Me gusta tu solución directiva. Sin embargo, ¿cómo lo haría? Desea cargar otra página y desplazarse a la ubicación del ancla al mismo tiempo. Sin angularjs, nominalmente sería href = "location # hash". Pero su directiva impide la recarga de la página.
CMCDragonkai

@CMCDragonkai con mi directiva, no estoy seguro porque hago uso de la llamada a $ anchorScroll, que parece que solo maneja el desplazamiento a un elemento actualmente en la página. Es posible que tenga que meterse con $ location o $ window para obtener algo que implique un cambio de página.
KhalilRavanna

2
Necesita anular la suscripción del evento locationChangeStart: var off = scope. $ On ('$ locationChangeStart', function (ev) {off (); ev.preventDefault ();});

Buena captura @EugeneTskhovrebov, seguí adelante y agregué eso a la respuesta en una edición.
KhalilRavanna

5

Intenta establecer un prefijo hash para rutas angulares $locationProvider.hashPrefix('!')

Ejemplo completo:

angular.module('app', [])
  .config(['$routeProvider', '$locationProvider', 
    function($routeProvider, $locationProvider){
      $routeProvider.when( ... );
      $locationProvider.hashPrefix('!');
    }
  ])

Esto no afecta el resultado. Sin embargo, sería dulce.
Rasmus

2
Estás seguro. ¿Por qué esto no funciona? Si el prefijo hash es!, El enrutamiento hash debe ser #! Por lo tanto, AngularJS debería detectar cuándo es solo un #hash, debería anclar el desplazamiento automáticamente y funcionar tanto para las URL del modo HTML5 como para las URL del modo hash.
CMCDragonkai

5

Resolví esto en la lógica de ruta para mi aplicación.

function config($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: '/partials/search.html',
      controller: 'ctrlMain'
    })
    .otherwise({
      // Angular interferes with anchor links, so this function preserves the
      // requested hash while still invoking the default route.
      redirectTo: function() {
        // Strips the leading '#/' from the current hash value.
        var hash = '#' + window.location.hash.replace(/^#\//g, '');
        window.location.hash = hash;
        return '/' + hash;
      }
    });
}

1
Esto no funciona: Error: [$ inyector: modulador] No se pudo crear una instancia del ángulo del módulo debido a: Error: 'controlador' no válido en cuando ()
geoidesic

5

Esta es una publicación antigua, pero pasé mucho tiempo investigando varias soluciones, así que quería compartir una más simple. Solo agregando target="_self"a la<a> etiqueta lo arregló para mí. El enlace funciona y me lleva a la ubicación correcta en la página.

Sin embargo, Angular todavía inyecta algo extraño con el # en la URL, por lo que puede tener problemas al usar el botón Atrás para la navegación y tal después de usar este método.


4

Este puede ser un nuevo atributo para ngView, pero he podido obtener enlaces hash de anclaje para trabajar con angular-routeel ngView autoscrollatributo y 'doble hash'.

ngView (ver desplazamiento automático)

(El siguiente código se usó con correa angular)

<!-- use the autoscroll attribute to scroll to hash on $viewContentLoaded -->    
<div ng-view="" autoscroll></div>

<!-- A.href link for bs-scrollspy from angular-strap -->
<!-- A.ngHref for autoscroll on current route without a location change -->
<ul class="nav bs-sidenav">
  <li data-target="#main-html5"><a href="#main-html5" ng-href="##main-html5">HTML5</a></li>
  <li data-target="#main-angular"><a href="#main-angular" ng-href="##main-angular" >Angular</a></li>
  <li data-target="#main-karma"><a href="#main-karma" ng-href="##main-karma">Karma</a></li>
</ul>

3

Podría hacer esto así:

<li>
<a href="#/#about">About</a>
</li>

2

Aquí hay una especie de solución sucia al crear una directiva personalizada que se desplazará al elemento especificado (con "preguntas frecuentes" codificadas)

app.directive('h3', function($routeParams) {
  return {
    restrict: 'E',
    link: function(scope, element, attrs){        
        if ('faq'+$routeParams.v == attrs.id) {
          setTimeout(function() {
             window.scrollTo(0, element[0].offsetTop);
          },1);        
        }
    }
  };
});

http://plnkr.co/edit/Po37JFeP5IsNoz5ZycFs?p=preview


De hecho, esto funciona, pero es, como dijiste, sucio. Muy sucio. Veamos si alguien más puede llegar a una solución más bonita, o tendré que ir con esto.
Rasmus

Angular ya tiene la funcionalidad scrollTo integrada a través de $anchorScroll, vea mi respuesta.
Ben Lesh

Se cambió el plunker para que esté menos sucio: usa $ location.path () por lo que no hay "preguntas frecuentes" codificadas en la fuente. Y también trató de usar $ anchorScroll, pero parece que debido al enrutamiento no funciona ...
Valentyn Shybanov

2
<a href="/#/#faq-1">Question 1</a>
<a href="/#/#faq-2">Question 2</a>
<a href="/#/#faq-3">Question 3</a>

2

Si no le gusta usar, ng-clickaquí hay una solución alternativa. Utiliza a filterpara generar la URL correcta en función del estado actual. Mi ejemplo usa ui.router .

El beneficio es que el usuario verá dónde va el enlace.

<a href="{{ 'my-element-id' | anchor }}">My element</a>

El filtro:

.filter('anchor', ['$state', function($state) {
    return function(id) {
        return '/#' + $state.current.url + '#' + id;
    };
}])

2

Mi solución con ng-route fue esta simple directiva:

   app.directive('scrollto',
       function ($anchorScroll,$location) {
            return {
                link: function (scope, element, attrs) {
                    element.click(function (e) {
                        e.preventDefault();
                        $location.hash(attrs["scrollto"]);
                        $anchorScroll();
                    });
                }
            };
    })

El html se ve así:

<a href="" scrollTo="yourid">link</a>

¿No tendría que especificar el atributo like scroll-to="yourid"y nombrar la directiva scrollTo(y acceder al atributo como attrs["scrollTo"]? Además, sin una inclusión explícita de jQuery, el controlador debe estar vinculado element.on('click', function (e) {..}).
Theodros Zelleke

1

Podría intentar usar anchorScroll .

Ejemplo

Entonces el controlador sería:

app.controller('MainCtrl', function($scope, $location, $anchorScroll, $routeParams) {
  $scope.scrollTo = function(id) {
     $location.hash(id);
     $anchorScroll();
  }
});

Y la vista:

<a href="" ng-click="scrollTo('foo')">Scroll to #foo</a>

... y ningún secreto para la identificación del ancla:

<div id="foo">
  This is #foo
</div>

1

Estaba tratando de hacer que mi aplicación Angular se desplazara a una carga de opon de anclaje y encontré las reglas de reescritura de URL de $ routeProvider.

Después de una larga experimentación me decidí por esto:

  1. registre un controlador de eventos document.onload desde la sección .run () del módulo de aplicación Angular.
  2. en el controlador, descubra qué se suponía que debía ser la etiqueta de anclaje original al hacer algunas operaciones de cadena.
  3. anule location.hash con la etiqueta de anclaje despojada (lo que hace que $ routeProvider la sobrescriba inmediatamente de nuevo con su regla "# /". Pero eso está bien, porque Angular ahora está sincronizado con lo que está sucediendo en la URL 4). $ anchorScroll ().

angular.module("bla",[]).}])
.run(function($location, $anchorScroll){
         $(document).ready(function() {
	 if(location.hash && location.hash.length>=1)    		{
			var path = location.hash;
			var potentialAnchor = path.substring(path.lastIndexOf("/")+1);
			if ($("#" + potentialAnchor).length > 0) {   // make sure this hashtag exists in the doc.                          
			    location.hash = potentialAnchor;
			    $anchorScroll();
			}
		}	 
 });


1

No estoy 100% seguro de si esto funciona todo el tiempo, pero en mi aplicación esto me da el comportamiento esperado.

Digamos que está en la página ACERCA DE y tiene la siguiente ruta:

yourApp.config(['$routeProvider', 
    function($routeProvider) {
        $routeProvider.
            when('/about', {
                templateUrl: 'about.html',
                controller: 'AboutCtrl'
            }).
            otherwise({
                redirectTo: '/'
            });
        }
]);

Ahora en tu HTML

<ul>
    <li><a href="#/about#tab1">First Part</a></li>
    <li><a href="#/about#tab2">Second Part</a></li>
    <li><a href="#/about#tab3">Third Part</a></li>                      
</ul>

<div id="tab1">1</div>
<div id="tab2">2</div>
<div id="tab3">3</div>

En conclusión

Incluyendo el nombre de la página antes del ancla hizo el truco para mí. Déjame saber sobre tus pensamientos.

Abajo

Esto volverá a representar la página y luego se desplazará al ancla.

ACTUALIZAR

Una mejor manera es agregar lo siguiente:

<a href="#tab1" onclick="return false;">First Part</a>

1

Obtenga su función de desplazamiento fácilmente. También es compatible con el desplazamiento animado / suave como una característica adicional. Detalles de la biblioteca de desplazamiento angular :

Github - https://github.com/oblador/angular-scroll

Bower :bower install --save angular-scroll

npm :npm install --save angular-scroll

Versión minfied - solo 9kb

Desplazamiento suave (desplazamiento animado) : sí

Scroll Spy : sí

Documentación - excelente

Demostración - http://oblador.github.io/angular-scroll/

Espero que esto ayude.


1

Basado en @Stoyan se me ocurrió la siguiente solución:

app.run(function($location, $anchorScroll){
    var uri = window.location.href;

    if(uri.length >= 4){

        var parts = uri.split('#!#');
        if(parts.length > 1){
            var anchor = parts[parts.length -1];
            $location.hash(anchor);
            $anchorScroll();
        }
    }
});

0

En el cambio de ruta, se desplazará hasta la parte superior de la página.

 $scope.$on('$routeChangeSuccess', function () {
      window.scrollTo(0, 0);
  });

pon este código en tu controlador.


0

En mi opinión, @slugslog lo tenía, pero cambiaría una cosa. En su lugar, usaría reemplazar para que no tenga que volver a configurarlo.

$scope.scrollTo = function(id) {
    var old = $location.hash();
    $location.hash(id).replace();
    $anchorScroll();
};

Búsqueda de documentos para "Método de reemplazo"


0

Ninguna de las soluciones anteriores me funciona, pero acabo de probar esto y funcionó,

<a href="#/#faq-1">Question 1</a>

Entonces me di cuenta de que necesito notificar a la página para comenzar con la página de índice y luego usar el ancla tradicional.


0

En algún momento en la aplicación angularjs, la navegación hash no funciona y las bibliotecas bootstrap jquery javascript hacen un uso extensivo de este tipo de navegación, para que funcione agregue target="_self"a la etiqueta de anclaje. p.ej<a data-toggle="tab" href="#id_of_div_to_navigate" target="_self">



0

Estoy usando AngularJS 1.3.15 y parece que no tengo que hacer nada especial.

https://code.angularjs.org/1.3.15/docs/api/ng/provider/ $ anchorScrollProvider

Entonces, lo siguiente funciona para mí en mi html:

<ul>
  <li ng-repeat="page in pages"><a ng-href="#{{'id-'+id}}">{{id}}</a>
  </li>
</ul>
<div ng-attr-id="{{'id-'+id}}" </div>

No tuve que hacer ningún cambio en mi controlador o JavaScript en absoluto.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.