Kun je een pad wijzigen zonder de controller opnieuw te laden in AngularJS?

Het is al eerder gevraagd en uit de antwoorden ziet het er niet goed uit. Ik zou graag een vraag willen stellen met deze voorbeeldcode als overweging…

Mijn app laadt het huidige item in de service die het levert. Er zijn verschillende controllers die de itemgegevens manipuleren zonder dat het item opnieuw wordt geladen.

Mijn controllers zullen het item opnieuw laden als het nog niet is ingesteld, anders zal het het momenteel geladen item van de service tussen controllers gebruiken.

Probleem: ik wil voor elke controller verschillende paden gebruiken zonder Item.html opnieuw te laden.

1) Is dat mogelijk?

2) Als dat niet mogelijk is, is er dan een betere benadering om een ​​pad per controller te hebben dan wat ik hier heb bedacht?

app.js

var app = angular.module('myModule', []).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/items', {templateUrl: 'partials/items.html',   controller: ItemsCtrl}).
      when('/items/:itemId/foo', {templateUrl: 'partials/item.html', controller: ItemFooCtrl}).
      when('/items/:itemId/bar', {templateUrl: 'partials/item.html', controller: ItemBarCtrl}).
      otherwise({redirectTo: '/items'});
    }]);

Item.html

<!-- Menu -->
<a id="fooTab" my-active-directive="view.name" href="#/item/{{item.id}}/foo">Foo</a>
<a id="barTab" my-active-directive="view.name" href="#/item/{{item.id}}/bar">Bar</a>
<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>

controller.js

// Helper function to load $scope.item if refresh or directly linked
function itemCtrlInit($scope, $routeParams, MyService) {
  $scope.item = MyService.currentItem;
  if (!$scope.item) {
    MyService.currentItem = MyService.get({itemId: $routeParams.itemId});
    $scope.item = MyService.currentItem;
  }
}
function itemFooCtrl($scope, $routeParams, MyService) {
  $scope.view = {name: 'foo', template: 'partials/itemFoo.html'};
  itemCtrlInit($scope, $routeParams, MyService);
}
function itemBarCtrl($scope, $routeParams, MyService) {
  $scope.view = {name: 'bar', template: 'partials/itemBar.html'};
  itemCtrlInit($scope, $routeParams, MyService);
}

Oplossing.

Status: door de zoekopdracht te gebruiken zoals aanbevolen in het geaccepteerde antwoord, kon ik verschillende URL’s opgeven zonder de hoofdcontroller opnieuw te laden.

app.js

var app = angular.module('myModule', []).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/items', {templateUrl: 'partials/items.html',   controller: ItemsCtrl}).
      when('/item/:itemId/', {templateUrl: 'partials/item.html', controller: ItemCtrl, reloadOnSearch: false}).
      otherwise({redirectTo: '/items'});
    }]);

Item.html

<!-- Menu -->
<dd id="fooTab" item-tab="view.name" ng-click="view = views.foo;"><a href="#/item/{{item.id}}/?view=foo">Foo</a></dd>
<dd id="barTab" item-tab="view.name" ng-click="view = views.bar;"><a href="#/item/{{item.id}}/?view=foo">Bar</a></dd>
<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>

controller.js

function ItemCtrl($scope, $routeParams, Appts) {
  $scope.views = {
    foo: {name: 'foo', template: 'partials/itemFoo.html'},
    bar: {name: 'bar', template: 'partials/itemBar.html'},
  }
  $scope.view = $scope.views[$routeParams.view];
}

directives.js

app.directive('itemTab', function(){
  return function(scope, elem, attrs) {
    scope.$watch(attrs.itemTab, function(val) {
      if (val+'Tab' == attrs.id) {
        elem.addClass('active');
      } else {
        elem.removeClass('active');
      }
    });
  }
});

De inhoud in mijn gedeeltelijke delen is verpakt met ng-controller=...


Antwoord 1, autoriteit 100%

Als u geen URL’s zoals #/item/{{item.id}}/fooen #/item/{{item.id}}/barmaar #/item/{{item.id}}/?fooen #/item/{{item.id}}/?barin plaats daarvan, je kunt je route voor /item/{{item.id}}/instellen om reloadOnSearchin te stellen op false(https://docs.angularjs.org/api/ngRoute/provider/$routeProvider). Dat vertelt AngularJS om de weergave niet opnieuw te laden als het zoekgedeelte van de url verandert.


Antwoord 2, autoriteit 78%

Als u het pad moet wijzigen, voegt u dit toe na uw .config in uw app-bestand.
Dan kun je $location.path('/sampleurl', false);doen om herladen te voorkomen

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}])

Tegoed gaat naar https://www.consolelog.io/angularjs-change -path-without-reloadingvoor de meest elegante oplossing die ik heb gevonden.


Antwoord 3, autoriteit 15%

waarom niet gewoon de ng-controller een niveau hoger zetten,

<body ng-controller="ProjectController">
    <div ng-view><div>

En zet de controller niet in de route,

.when('/', { templateUrl: "abc.html" })

het werkt voor mij.


Antwoord 4, autoriteit 11%

Voor degenen die pad() willen wijzigen zonder controllers opnieuw te laden –
Hier is de plug-in: https://github.com/anglibs/angular-location-update

Gebruik:

$location.update_path('/notes/1');

Gebaseerd op https://stackoverflow.com/a/24102139/1751321

P.S. Deze oplossing https://stackoverflow.com/a/24102139/1751321bevat een bug
na path(, false) aangeroepen – het zal browsernavigatie terug/vooruit verbreken totdat path(, true) aangeroepen


Antwoord 5, autoriteit 8%

Hoewel dit bericht oud is en een antwoord is geaccepteerd, lost het gebruik van reloadOnSeach=false het probleem niet op voor degenen onder ons die het werkelijke pad moeten veranderen en niet alleen de parameters. Hier is een eenvoudige oplossing om te overwegen:

Gebruik ng-include in plaats van ng-view en wijs uw controller toe in de sjabloon.

<!-- In your index.html - instead of using ng-view -->
<div ng-include="templateUrl"></div>
<!-- In your template specified by app.config -->
<div ng-controller="MyController">{{variableInMyController}}</div>
//in config
$routeProvider
  .when('/my/page/route/:id', { 
    templateUrl: 'myPage.html', 
  })
//in top level controller with $route injected
$scope.templateUrl = ''
$scope.$on('$routeChangeSuccess',function(){
  $scope.templateUrl = $route.current.templateUrl;
})
//in controller that doesn't reload
$scope.$on('$routeChangeSuccess',function(){
  //update your scope based on new $routeParams
})

Het enige nadeel is dat u het kenmerk resolve niet kunt gebruiken, maar dat is vrij eenvoudig te omzeilen. Je moet ook de status van de controller beheren, zoals logica gebaseerd op $routeParams als de route binnen de controller verandert als de bijbehorende url verandert.

Hier is een voorbeeld: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview

p>


Antwoord 6, autoriteit 2%

Ik gebruik deze oplossing

angular.module('reload-service.module', [])
.factory('reloadService', function($route,$timeout, $location) {
  return {
     preventReload: function($scope, navigateCallback) {
        var lastRoute = $route.current;
        $scope.$on('$locationChangeSuccess', function() {
           if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
              var routeParams = angular.copy($route.current.params);
              $route.current = lastRoute;
              navigateCallback(routeParams);
           }
        });
     }
  };
})
//usage
.controller('noReloadController', function($scope, $routeParams, reloadService) {
     $scope.routeParams = $routeParams;
     reloadService.preventReload($scope, function(newParams) {
        $scope.routeParams = newParams;
     });
});

Deze benadering behoudt de functionaliteit van de terugknop en je hebt altijd de huidige routeParams in de sjabloon, in tegenstelling tot sommige andere benaderingen die ik heb gezien.


Antwoord 7

Bovenstaande antwoorden, waaronder die van GitHub, hadden wat problemen met mijn scenario en ook de terugknop of de directe url-wijziging van de browser was het opnieuw laden van de controller, wat ik niet leuk vond. Ik ging uiteindelijk voor de volgende aanpak:

1.Definieer een eigenschap in routedefinities, genaamd ‘noReload’ voor die routes waarvan je niet wilt dat de controller opnieuw laadt bij routewijziging.

.when('/:param1/:param2?/:param3?', {
    templateUrl: 'home.html',
    controller: 'HomeController',
    controllerAs: 'vm',
    noReload: true
})

2.Zet in de run-functie van je module de logica die op die routes controleert. Het zal alleen herladen voorkomen als noReloadtrueis en de vorige routecontroller hetzelfde is.

fooRun.$inject = ['$rootScope', '$route', '$routeParams'];
function fooRun($rootScope, $route, $routeParams) {
    $rootScope.$on('$routeChangeStart', function (event, nextRoute, lastRoute) {
        if (lastRoute && nextRoute.noReload 
         && lastRoute.controller === nextRoute.controller) {
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                un();
                // Broadcast routeUpdate if params changed. Also update
                // $routeParams accordingly
                if (!angular.equals($route.current.params, lastRoute.params)) {
                    lastRoute.params = nextRoute.params;
                    angular.copy(lastRoute.params, $routeParams);
                    $rootScope.$broadcast('$routeUpdate', lastRoute);
                }
                // Prevent reload of controller by setting current
                // route to the previous one.
                $route.current = lastRoute;
            });
        }
    });
}

3.Luister ten slotte in de controller naar de $routeUpdate-gebeurtenis, zodat je kunt doen wat je moet doen als de routeparameters veranderen.

HomeController.$inject = ['$scope', '$routeParams'];
function HomeController($scope, $routeParams) {
    //(...)
    $scope.$on("$routeUpdate", function handler(route) {
        // Do whatever you need to do with new $routeParams
        // You can also access the route from the parameter passed
        // to the event
    });
    //(...)
}

Houd er rekening mee dat je met deze aanpak geen dingen in de controller verandert en vervolgens het pad dienovereenkomstig bijwerkt. Het is andersom. Je verandert eerst het pad en luistert vervolgens naar de $routeUpdate-gebeurtenis om dingen in de controller te wijzigen wanneer de routeparameters veranderen.

Dit houdt de zaken eenvoudig en consistent omdat je dezelfde logica kunt gebruiken, zowel wanneer je het pad verandert (maar zonder dure $http-verzoeken als je wilt) als wanneer je de browser volledig herlaadt.


Antwoord 8

Hier is mijn volledigere oplossing die een paar dingen oplost die @Vigrond en @rahilwazir hebben gemist:

  • Als de zoekparameters werden gewijzigd, werd het uitzenden van een $routeUpdateverhinderd.
  • Als de route feitelijk ongewijzigd wordt gelaten, wordt $locationChangeSuccessnooit geactiveerd, waardoor de volgenderoute-update wordt voorkomen.
  • Als er in dezelfde samenvattingscyclus nog een updateverzoek was, dit keer met de wens om opnieuw te laden, zou de gebeurtenishandler dat opnieuw laden annuleren.

    app.run(['$rootScope', '$route', '$location', '$timeout', function ($rootScope, $route, $location, $timeout) {
        ['url', 'path'].forEach(function (method) {
            var original = $location[method];
            var requestId = 0;
            $location[method] = function (param, reload) {
                // getter
                if (!param) return original.call($location);
                # only last call allowed to do things in one digest cycle
                var currentRequestId = ++requestId;
                if (reload === false) {
                    var lastRoute = $route.current;
                    // intercept ONLY the next $locateChangeSuccess
                    var un = $rootScope.$on('$locationChangeSuccess', function () {
                        un();
                        if (requestId !== currentRequestId) return;
                        if (!angular.equals($route.current.params, lastRoute.params)) {
                            // this should always be broadcast when params change
                            $rootScope.$broadcast('$routeUpdate');
                        }
                        var current = $route.current;
                        $route.current = lastRoute;
                        // make a route change to the previous route work
                        $timeout(function() {
                            if (requestId !== currentRequestId) return;
                            $route.current = current;
                        });
                    });
                    // if it didn't fire for some reason, don't intercept the next one
                    $timeout(un);
                }
                return original.call($location, param);
            };
        });
    }]);
    

Antwoord 9

Voeg het volgende toe aan de head-tag

 <script type="text/javascript">
    angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
  </script>

Dit voorkomt het herladen.


Antwoord 10

Er is een eenvoudige manier om van pad te veranderen zonder opnieuw te laden

URL is - http://localhost:9000/#/edit_draft_inbox/1457

Gebruik deze code om de URL te wijzigen, pagina wordt niet omgeleid

Tweede parameter “false” is erg belangrijk.

$location.path('/edit_draft_inbox/'+id, false);

Antwoord 11

Ik kon geen van de antwoorden hier laten werken. Als een vreselijke hack sla ik een tijdstempel op in de lokale opslag wanneer ik de route verander, en controleer bij pagina-initialisatie of deze tijdstempel is ingesteld en recent is, in dat geval activeer ik geen enkele initialisatieactie.

In controller:

window.localStorage['routeChangeWithoutReloadTimestamp'] = new Date().getTime();
$location.path(myURL);

In configuratie:

.when(myURL, {
            templateUrl: 'main.html',
            controller:'MainCtrl',
            controllerAs: 'vm',
            reloadOnSearch: false,
            resolve:
            {
                var routeChangeWithoutReloadTimestamp =
                    window.localStorage['routeChangeWithoutReloadTimestamp'];
                var currentTimestamp = new Date().getTime();
                if (!routeChangeWithoutReloadTimestamp ||
                        currentTimestamp - routeChangeWithoutReloadTimestamp >= 5000) {
                    //initialization code here
                }
                //reset the timestamp to trigger initialization when needed
                window.localStorage['routeChangeWithoutReloadTimestamp'] = 0;
            }
});

Ik heb een tijdstempel gebruikt in plaats van een boolean, voor het geval de code wordt onderbroken voordat ik de kans krijg om de opgeslagen waarde opnieuw in te voeren voordat de route wordt gewijzigd. Het risico op botsingen tussen tabbladen is erg laag.

Other episodes