Search code examples
angularjsangular-ui-routerangularjs-ng-include

Set dynamic URL on state in AngularJS with Ui-Router


I have an ng-include rendering partials on my page but as is the nature of ng-include I do not need a state to load the partials. I would like the URL to update when the ng-include updates its view so my analytics track it as a page change. I am wondering the best way to do this.

Also I am using angular-ui-router, which makes ng-include irrelevant because of nested states; until one comes across my situation: I have a large number of partials loading into the ng-include, which is lovely because all I need to provide is a path to the partial and not a massive number of different states to my $stateProvider file; but then there is no custom url. As such what I was wondering is the following:

Can an Ng-include update the page url when it loads a partial without having a state defined for that partial view

OR

something that would solve my problem altogether: is there a way to add states that have a url which can be dynamically updated? like:

.state( 'test',  {
        url: function( stateId ){
    // where stateId is somehow passed into the $stateParams of the new state
},
    templateUrl: "templates/views/test.html"
})

here is the setup so far for my ng-include, if you have any ideas on how to get the url to change when the ng-include renders a different partial I would be most pleased.


::JS::

// states
 // self executing function
(function() {

var app = angular.module( 'app', [ 'ui.router', 'ngAnimate' ] );

app.config( function( $stateProvider, $urlRouterProvider ) {

  // if url not defined redirect to login
  $urlRouterProvider.when( '', "/home" );
  // if nonexistant url defined redirect to sign-in
  $urlRouterProvider.otherwise( "/home" );

  // Now set up the states
  $stateProvider
    .state('home', {
      url: "/home",
      templateUrl: "templates/views/home.html"
    });
}()); // self executing function end

// factory
//self executing container function
(function() {
var listFactory = function( ) {
  // declare factory for return at end of function
  var factory = {};

  var list = 
    { 
        list1 : [
            { 
                name: 'first',
                url: 'templates/views/partials/view1.html',
                id: '1'
            },
            {
                name: 'second',
                url: 'templates/views/partials/view2.html',
                id: '2'
            },
            {
                name: 'third',
                url: 'templates/views/partials/view3.html',
                id: '3'
            },
            {
                name: 'fourth',
                url: 'templates/views/partials/view4.html',
                id: '4'
            }
        ]
    };

    factory.getList = function(){
      return list;
    };

    // return factory object to access data
    return factory;
};

  angular.module( 'app' ).factory( 'listFactory', listFactory );

}());

// controller
//self executing container function
(function() {

    var listController = function ( $scope, listFactory ) {

        $scope.listFactory = listFactory.getList();
        // set a default list state
        $scope.currentList = 'templates/views/partials/default.html';

        // function sets list url on click to new scope item for rendering partial view
        $scope.setCurrentList = function( url ) {
            $scope.currentList = url;
        };
    };

    listController.$inject = [ '$scope', 'listFactory' ];

    angular.module('app')
      .controller('listController', listController);

}());

::HTML::

<!-- this is loaded in the home state.  -->

<div>
    <div id="{{ list.id }}" ng-repeat="list in list.list1" ng-click="setCurrentList( list.url )">
        <p>{{ list.name }}</p>
    </div>
</div>
<div id="list-view-container">
    <div class="rotateViewAnimate" ng-include src="currentList"></div>
</div>

::Rather irrelevant CSS::

/* Have to set height explicity on ui-view-container
to prevent collapsing during animation*/
#list-view-container {
    position: relative;
    min-height: 500px;
    overflow: hidden;
}    

/* rotate in / out animation transition */
.rotateViewAnimate.ng-enter, .rotateViewAnimate.ng-leave {
  /* settings so animations display together */
  position: absolute!important;
  left: 0!important;
  right: 0!important;
  -webkit-transition: 0.5s ease-in-out all;
  -moz-transition: 0.5s ease-in-out all;
  -o-transition: 0.5s ease-in-out all;
  transition: 0.5s ease-in-out all;
  transform-style: preserve-3d;
}

.rotateViewAnimate.ng-enter {
    transform: rotateY(90deg);
    opacity: 0;
}

.rotateViewAnimate.ng-enter-active {
    transform: rotateY(0deg);
    opacity:1;
}

.rotateViewAnimate.ng-leave {
    transform: rotateY(0deg);
    opacity:1;
}

.rotateViewAnimate.ng-leave-active {
    transform: rotateY(90deg);
    opacity:0;
}






Solution


  • SOLVED


    The Issue I was looking to solve is gone over in some detail in the ui-router docs; found here:

    https://github.com/angular-ui/ui-router/wiki

    I used the following method to achieve the effect mentioned here:

    "Set a Dynamic URL on a state using ui-router and angularJS"

    Going through the code displayed in the question I will adjust it accordingly with my solution.

    // states
    // self executing function
    (function() {
    
    var app = angular.module( 'app', [ 'ui.router', 'ngAnimate' ] );
    
    app.config( function( $stateProvider, $urlRouterProvider ) {
    
      // if url not defined redirect to login
      $urlRouterProvider.when( '', "/home" );
      // if nonexistant url defined redirect to sign-in
      $urlRouterProvider.otherwise( "/home" );
    
      // Now set up the states
      $stateProvider
        .state('home', {
          url: "/home",
          templateUrl: "templates/views/home.html"
        })
        .state( 'home.urlChanger', {
            url: '/urlChanger/name=:name&url=:url',
            params: {
                'spareParam' : 'whateverString'
            },
            views: {
                'viewName@home': {
                    templateUrl: function ( $stateParams ){
                        return $stateParams.url;
                    },
                    controller: "mainController"
                }
            }
        });
    }()); // self executing function end
    

    in the above state declaration the state "home.urlChanger" has a url with special characters which are given values from information passed to the $stateProvider variable. a VERY IMPORTANT note here is that if one wants to be able to access the item stored inside $stateParams it must be passed into the URL. as such, in my controller I use the following function to pass the information I want available in the state using the $state.go(); method as follows:

    $scope.setParams = function( location ) {
            var urlname = object.stored.in.factory.NAME,
                url = object.from.factory.URL, // path to html file passed to be used in termplateurl from state
                // set stateParams.name to be used in URL
                $stateParams.name = urlName;
    
                // set params to be passed into URL when $state.go(); is called, they will be available for use then and update the URL with the state setup from above.
                $state.go( location, { name: $stateParams.name, url: url } );
            }; 
    

    as can be seen from the above example I tried storing into $stateparams.name in order to see if it would become available in stateparams, but as is expected when the state.go(); is called a new state is loaded and the previous $stateparams object is no longer available. hence why the information must be passed during $state.go in order to store it into the newly transitioned states $stateParams object.

    so the html for causing this to occur could be an ng-click on any element that calls the setparams( location ) function, which will set your variables into $stateParams by passing them through the URL and making them available in your state ie: a dynamic URL set by the state itself.

    hopefully someone finds that a bit helpful, it took me some time to work through myself.