it-swarm.dev

Angular-ui.router: Aggiorna URL senza aggiornamento della vista

Ho una Angular SPA che presenta una varietà di elenchi di consigli e una mappa di Google delle località, sulla base di diversi tagli di alcuni dati sui ristoranti (vedi m.amsterdamfoodie.nl ). Voglio che ognuna di queste liste abbia il proprio URL. Per consentire a Google di eseguire la scansione dei diversi elenchi, utilizzo i tag <a> per la navigazione offcanvas.

Al momento il tag <a> causa un aggiornamento della vista, che è molto evidente con la mappa.

  • Posso evitarlo usando ng-click e $event.preventDefault() (vedi frammenti di codice sotto), ma poi ho bisogno di implementare un mezzo per aggiornare l'URL del browser.
  • Ma provando il $state di Angular o il history.pushstate del browser, finisco per attivare i cambiamenti di stato e la visualizzazione aggiorna ...!

La mia domanda è quindi come posso aggiornare un modello e l'URL, ma senza aggiornare la vista? (Vedi anche Angolare/UI-Router - Come posso aggiornare l'URL senza aggiornare tutto? )

Ho sperimentato molti approcci e attualmente ho questo html

<a href="criteria/price/1" class="btn btn-default" ng-click="main.action($event)">Budget</a>

Nel controller:

this.action = ($event) ->
    $event.preventDefault()
    params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)

    # seems to cause a view refresh
    # history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);

    # seems to cause a view refresh
    # $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}

    updateModel(...)

E, quello che penso stia accadendo è che sto attivando il codice $stateProvider:

angular.module 'afmnewApp'
.config ($stateProvider) ->
  $stateProvider
  .state 'main',
    url: '/'
    templateUrl: 'app/main/main.html'
    controller: 'MainCtrl'
    controllerAs: 'main'
  .state 'criteria',
    url: '/criteria/:criteria/:q'
    templateUrl: 'app/main/main.html'
    controller: 'MainCtrl'
    controllerAs: 'main'

Un possibile indizio è quello con il seguente codice se carico ad es. http://afmnew.herokuapp.com/criteria/cuisine/italian quindi la vista si aggiorna mentre navighi, mentre se carico http://afmnew.herokuapp.com/ non ci sono aggiornamenti, ma non gli aggiornamenti URL invece. Non capisco perché ciò stia accadendo.

23
Simon H

Sulla base delle nostre discussioni precedenti, voglio darvi un'idea, su come utilizzare UI-Router qui. Credo, comprendo correttamente la tua sfida ... C'è un esempio funzionante . Se questo non è completo, per favore prendilo come ispirazione

DISCLAIMER: Con un plunker, non ero in grado di ottenere questo: http://m.amsterdamfoodie.nl/ , ma il principio dovrebbe essere in quello example similar

Quindi, esiste una definizione di stato (abbiamo solo due stati)

  $stateProvider
    .state('main', {
        url: '/',
        views: {
          '@' : {
            templateUrl: 'tpl.layout.html',
            controller: 'MainCtrl',
          },
          '[email protected]' : { templateUrl: 'tpl.right.html',}, 
          '[email protected]' : {
            templateUrl: 'tpl.map.html',
            controller: 'MapCtrl',
          },
          '[email protected]' : {
            templateUrl: 'tpl.list.html',
            controller: 'ListCtrl',
          },
        },
      })
    .state('main.criteria', {
        url: '^/criteria/:criteria/:value',
        views: {
          'map' : {
            templateUrl: 'tpl.map.html',
            controller: 'MapCtrl',
          },
          'list' : {
            templateUrl: 'tpl.list.html',
            controller: 'ListCtrl',
          },
        },
      })
}];

Questo sarebbe il nostro principale tpl.layout.html

<div>

  <section class="main">

    <section class="map">
      <div ui-view="map"></div>
    </section>

    <section class="list">
      <div ui-view="list"></div>
    </section>

  </section>

  <section class="right">
    <div ui-view="right"></div>
  </section>

</div>

Come possiamo vedere, lo stato principale punta a queste viste nidificate dello stato principale: 'viewName @ main', ad es. '[email protected]'

Anche la sottoview, main.criteria, si inserisce nelle viste layout

Il suo url inizia con un segno ^ (url : '^/criteria/:criteria/:value'), che consente di avere la barra / per la barra principale e non raddoppiata per il figlio

E ci sono anche i controller, sono qui un po 'ingenui, ma dovrebbero mostrare che sullo sfondo potrebbe esserci un vero carico di dati (basato su criteri).

La cosa più importante qui è che il PARENT MainCtrl crea $scope.Model = {}. Questa proprietà sarà (grazie all'eredità) condivisa tra genitori e figli. Ecco perché tutto funzionerà:

app.controller('MainCtrl', function($scope)
{
  $scope.Model = {};
  $scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];  
  $scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
  $scope.Model.list = []
  $scope.Model.data
    .sort( $scope.Model.randOrd )
    .forEach(function(i) {$scope.Model.list.Push(i + " - " + $stateParams.value || "root")})
  $scope.Model.selected = $scope.Model.list[0];
  $scope.Model.select = function(index){
    $scope.Model.selected = $scope.Model.list[index];  
  }
})

Questo dovrebbe avere un'idea di come possiamo usare le funzionalità fornite per noi dall'interfaccia utente:

Controlla l'estratto sopra qui , in l'esempio di lavoro

Estendi: nuovo plunker qui

Se non vogliamo che la vista della mappa venga ricreata, possiamo semplicemente omettere quella che è la definizione dello stato del bambino:

.state('main.criteria', {
    url: '^/criteria/:criteria/:value',
    views: {
      // 'map' : {
      //  templateUrl: 'tpl.map.html',
      //  controller: 'MapCtrl',
      //},
      'list' : {
        templateUrl: 'tpl.list.html',
        controller: 'ListCtrl',
      },
    },
  })

Ora la nostra mappa VISTA riceverà solo modifiche nel modello (potrebbe essere guardato) ma la visualizzazione e il controller non verranno rerenderizzati

INOLTRE, c'è un altro plunker http://plnkr.co/edit/y0GzHv?p=preview che usa controllerAs

.state('main', {
    url: '/',
    views: {
      '@' : {
        templateUrl: 'tpl.layout.html',
        controller: 'MainCtrl',
        controllerAs: 'main',        // here
      },
      ...
    },
  })
.state('main.criteria', {
    url: '^/criteria/:criteria/:value',
    views: {
      'list' : {
        templateUrl: 'tpl.list.html',
        controller: 'ListCtrl',
        controllerAs: 'list',      // here
      },
    },
  })

e che potrebbe essere usato in questo modo:

<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>

L'ultimo plunker è qui

11
Radim Köhler

Questo è un esempio del modo in cui andare se ho capito bene:

$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});

Da utente l-liava-l nel problema https://github.com/angular-ui/ui-router/issues/64

Puoi controllare l'API $state qui: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state . $ State

16
John Bernardsson

è possibile utilizzare l'ereditarietà dell'ambito per aggiornare l'URL senza visualizzazione di aggiornamento

$stateProvider
            .state('itemList', {
                url: '/itemlist',
                templateUrl: 'Scripts/app/item/ItemListTemplate.html',
                controller: 'ItemListController as itemList'
                //abstract: true //abstract maybe?
            }).state('itemList.itemDetail', {
                url: '/:itemName/:itemID',
                templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
                controller: 'ItemDetailController as itemDetail',
                resolve: {
                    'CurrentItemID': ['$stateParams',function ($stateParams) {
                        return $stateParams['itemID'];
                    }]
                }
            })

se la vista figlio è all'interno della vista genitore, entrambi i controllori condividono lo stesso ambito . in modo da poter posizionare una vista utente fittizia (o necessaria) nella vista genitore che verrà popolata dalla vista secondaria.

e inserire a 

$scope.loadChildData = function(itemID){..blabla..};

funzione nel controllore genitore che verrà richiamato dal controllore figlio sul carico del controllore. così quando un utente fa clic 

<a ui-sref="childState({itemID: 12})">bla</a> 

solo il controller figlio e la vista figlio verranno aggiornati. quindi è possibile chiamare la funzione dell'ambito genitore con i parametri necessari.

2
bahadir

La risposta breve ha finito per essere fare non inserire la mappa all'interno di una vista che cambia. La risposta accettata fornisce molti più dettagli su come strutturare una pagina con sotto-viste, ma il punto chiave non è quello di rendere la parte della mappa della vista ma di collegare il suo comportamento a una vista che cambia e di usare un Controller per aggiornare le icone del mercato.

0
Simon H