Angular ui-router 1.0 change view in $transition - angularjs

I would like somebody's help on how to change the view within a transition.
My problem is that I need to load a view dynamically. So I create a new view (this seems to be working):
var viewname = 'view-'+Date.now();
var element = angular.element(document.querySelector('#view-container'));
var scope = element.scope();
var elementString = '<div id="'+viewname+'" ui-view="'+viewname+'" class="viewunique"></div>';
$compile(element.append(elementString))(scope);
This will add the view to the $view variable and seems to be working.
My question is: How can I use this new view in a statechange. I tried using the $transition$.$to() like:
$transitions.onStart( {}, function($transition$) {
$transition$.$to().views = {
templateUrl : templateUrl
};
});
where viewname is the name of the created view, and templateUrl is the correct template. The template will have it's own controller and stuff. This will overwrite the view for this state, but doesn't really work or do anything.
Somehow I would like to change the views for the state within the transition to this state so that the dynamically created view will be used. Is this possible?
Does someone know how this can be done?

Okay, I did some research on what you exactly wanted. If I understand correctly you want to change views during a state change, with each view having it's own scope and controller.
This seems to me like you want multiple states running besides each other. Perhaps you could take a look at ui-router-extras:
https://github.com/christopherthielen/ui-router-extras
This package will allow you to use parallel states and nested states, which I believe is what you want/need.
Take a loot at https://christopherthielen.github.io/ui-router-extras/#/home for more info and working examples.

Related

dynamic header/menu in angularjs

While transitioning an existing angular site, I encountered an annoying problem. The initial symptom was that a certain controller was not running it's initialize function immediately following the login. I logged and I tracked, and eventually I realized it was a design flaw of the page. Essentially, index.html contains a <header>, <ng-view>, and <footer>. There are a couple of ng-if attributes that live in the header that I want to evaluate after the login, but since the view is the only thing that is reloaded, it was not reinitializing the header controller, and thus not updating the ng-if values.
Then I was reminded of ngInclude, which seems like the perfect solution, until I got it hooked up and realize that doesn't work either. It loads the template the first time, and doesn't reinitialize when the view changes. So then I got the bright idea of passing the HeaderController to another controller or service, and controlling this one stubborn boolean value through a proxy of sorts. That also didn't work. Then I tried putting a function and a boolean into another service, and mirroring that property in the header controller, but thus far I have not gotten this working.
I have done plenty of research about multiple views in the index, and so far I hear a lot about this ui-router, but I'm still not convinced that is the way I want to go. It does not seem to be a simple solution. I have not tried putting the ng-include into the templates yet either, because then I feel like that is going back in time to when we had to update 100 pages every time we changed the menu.
I lost a whole day to this. If anyone could tell me how to trigger the evaluation of this one property in my header controller which I would like to live outside the other templates, please let me know!
Ok so you need to know in your HeaderController when the view has reloaded. There's a number of ways of doing this but the easier and maybe the more correct in this particular case is with an event.
So when you are refreshing the view you just do this, let's say you need the new value of ob1 and ob2 variables.
// ViewController
$rootScope.$emit('viewRefresh', {ob1: 'newvalue1', ob2: 'newvalue2'});
And in your HeaderController you need to listen for that event, and set on your $scope the new values for those attrs (if you're not using controller as syntax).
// HeaderController
$rootScope.$on('viewRefresh', function onRefresh(event, data) {
$scope.ob1 = data.ob1;
$scope.ob2 = data.ob2;
})
Another Solution
Sharing a Promise through a Service (using $q)
function HeaderService($q) {
var defer = $q.defer();
return {
getPromise: function() {return defer.promise},
notify: function(data) {defer.notify(data)}
}
}
function HeaderController(HeaderService) {
var vm = this;
HeaderService.getPromise().then(function(data) {
vm.ob1 = data.ob1;
vm.ob2 = data.ob2;
})
}
function ViewController(HeaderService) {
var data = {ob1: 'newvalue1', ob2: 'newvalue2'};
HeaderService.notify(data)
}

Angularjs: Can i have one route with multiple views?

Consider a certain route let's say myapp\profile
which has two modes (buyer/seller)
What i would like to achieve is:
keep the same route url for both modes
Alternate the view with different HTML files (lets say buyer.html, seller.html), of course each view has it's view model.
Sharing some logic between the two modes.
I would like to have a controller/logic to each mode
What i already considered:
Thought about using ui-router's sub states, but I dont want to change the url.
Thought about creating this 'profile' route and while navigating to it, figure the mode (buyer/seller), and then $state.go to a new state (but again, i would like to keep same route name at the end so it's not ok)
Ideally thought i could navigate to my shared controller and then render the correct view and controller, but this idea kinda messed up me.
Could you share what is a clean way of doing this?
most use cases
Normally, in order to dynamically select a template for a state, you can use a function:
state = {
.....
templateUrl: function($stateParams){
if ($stateParams.isThis === true)
return 'this.html'
else
return 'that.html'
}
}
but...
Unfortunately you can't pass other injectables to the templateUrl function. UI.Router only passes $stateParams. You don't want to alter the URL in anyway so you can't use this.
when you need to inject more than $stateParams
But you can leverage templateProvider instead. With this feature, you can pass a service to your templateProvider function to determine if your user is a buyer or seller. You'll also want to use UI.Router's $templateFactory service to easily pull and cache your template.
state = {
....
templateProvider: function($templateFactory, profileService){
var url = profileService.isBuyer ? 'buyer.html' : 'seller.html';
return $templateFactory.fromUrl(url);
}
}
Here it is working in your plunkr - http://plnkr.co/edit/0gLBJlQrNPUNtkqWNrZm?p=preview
Docs:
https://github.com/angular-ui/ui-router/wiki#templates
http://angular-ui.github.io/ui-router/site/#/api/ui.router.util.$templateFactory

Updating URL in Angular JS without re-rendering view

I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.

When using Marionette.js when should I call vent.trigger('app:show', Controller.layout)?

Of course the answer is "Use it when you want to show the app". Ok that is fair enough. But what about subviews? I am using Ted's example: https://github.com/t2k/backbone.marionette-RequireJS. That example only has one controller setup. I have six controllers which I copied the libraryController in Ted's example. Each example has the following code:
var _initializeLayout = function() {
console.log('initialize Start Layout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("startLayout:rendered");
});
vent.trigger('app:show', Controller.layout); <!-- is this needed for each?
};
So I have that code in each of my controllers. The StartLayout has two regions that have their own views that are the entry points to the InspectorController and the PlayerController.
Each of those controllers has:
vent.trigger('app:show', Controller.layout);
So it would seem to me that I may be calling 'app:show' more than needed. Once for every Controller that needs initializing.
Is this necessary? I can understand perhaps calling that when I'm dealing with direct child views of the app but if I'm deep into PlayerController and the app view isn't visible it seems like overkill.
Thanks,
Andrew
Try not to think of "calling" app:show. It's not a function, it's an event. An event can have an arbitrary number of subscriptions listening for it. In the case of this application, there is only one listener on that event:
vent.on('app:show', function(appView) {
app.content.show(appView);
});
In this case, it's telling the content region to display whatever view is included in the event as appView. So, if you want to replace the content region with your own view, you should trigger app:show with a parameter of whatever view you want the content region to display.
content is bound to a DOM element, and whenever you call content.show(someView), the contents of that DOM element will be replaced by whatever is generated by someView.render().el.
I would suggest reading up on Layouts, Regions, and Events.
Hope this helps.

Re-Rendering Handlebars partial from backbone view

I have a view, which holds handlebars template.
that template consist of another partial template.
that partial template holds a list of results, which i am using in different parts of my app.
anyhow, when trying to filter the results, i'd like to render only that part. meaning the backbone view should not render the whole view just the partial.
can it be done?
Yes, it's possible. The easiest way is to execute the whole template as you do when rendering the complete view, but only replace the the part you need in the view's el.
Something like:
template: Handlebars.compile(templateHtml),
render: function() {
//let's say your render looks something like this
this.$el.html(this.template(this.model.toJSON());
},
renderList: function() {
var html = this.template(this.model.toJSON());
var selector = "#list";
//replace only the contents of the #list element
this.$el.find(selector).replaceWith($(selector, html));
}
Depending on how dynamic your template is, you may have to call this.delegateEvents() after replacing the list for the view's events to work correctly.
Edit based on comments:
To clarify, the method I propose here does execute the view's main handlebars template again, but it doesn't render the whole view again.
Step by step:
Execute the Handlebars template function as you do in normal render.
var html = this.template(this.model.toJSON());
The variable html now contains a string of HTML markup. Nothing has yet been rendered.
Define a selector for the element, which you would like to re-render.
var selector = "#list";
Find the DOM element to replace. This presumes that you have already rendered the view once. Otherwise there will be no #list element within this.$el.
this.$el.find(selector)
Find the corresponding element in the templated html string, and replace the existing element with the new one:
.replaceWith($(selector, html));
This will only replace the #list element that's currently on the page. Anything outside #list will not be re-rendered or touched in any way.
The main reason I propose you do it this way instead of executing and rendering the partial template separately is that your view doesn't need to know anything about the implementation details of the template and the templating engine. All it needs to know that there is an element #list. I believe this is a cleaner solution, and keeps your template details separate from your view logic.

Resources