Load view/controller programmatically - angularjs

How do I, at runtime, load a view / controller?
Say for example I have a list of content like so
ctrl.pages = [{
controller:"myController",
templateUrl : "/app/view.html"
},{
controller:"myController2",
templateUrl : "/app/view2.html"
}]
How do I then load those views and controllers? Is it simply just a case of using "ng-include" or is there a better way? Libraries such as Angular Material dont seem to do it this way.
<ng-include ng-repeat="page in ctrl.pages"
ng-controller="{{page.controller}}"
src="{{page.templateUrl }}"></ng-include>

That seems like either a case for a template expanding directive, see the Angular docs here. Or if those pages represent different "states" of the application, somewhere a user might visit directly via a URL, they'd be a good candidate for ui-router.

check this plnk out.
I added a select of available views and based on the selection ng-include's src is updated.
I avoided initializing the controller(s) intentionally, you may include them.

Related

Render initial Angular ng-view on server-side and take it from there

I want to avoid the latency in display of initial JavaScript-rendered views. I want the user to see content immediately and have Angular take it from there. I do not want to just replace this ng-view when Angular ngRoute kicks in as a blink will likely happen. I only want it to replace it once the user hits another route.
Let's imagine this is the base route '/'. This would already exist in my HTML, rendered from the server.
<div ng-view>
<h1>Welcome. I am the first view.</h1>
<p>Please do not replace me until a user has triggered another route.</p>
</div>
I know that a common approach is to have some server-side code in an ng-view and when Angular loads it just replaces it. This is not what I'm looking to do. I want Angular to load and understand that this is actually already my first view.
Any creative ideas as to how to do this? I've looked at the source code- no luck. Maybe even a way to have Angular only replace the HTML if it is different.
Edit:
I am not looking to render templates on the server-side for use as Angular templates. I am looking to render my entire index.html on the server-side, and that would already contain everything the user needs to see for this initial base route.
6-10 seconds on any mobile is quite bad. I wouldn't blame angular here, angular is only 30kb, if that is still too slow, you've chosen wrong framework for task.
Use profiling tools to understand what is going on.
How big is the application you're dealing with?
Can you split the application into sub-applications?
Are you doing minification already for CSS & JS?
Are you lazy loading all your views & controllers?
Are you compressing everything? (gzip)
Anyways, it is possible to do pre-processing on server-side for your index.html
You can do pre-processing using nodejs, for example, and cache the pre-processed index.html.
Your nodejs pre-processor could do (pseudo-code):
function preprocessIndexHtml(queryString) {
if(cached[queryString])) return cached[queryString];
// assume angular.js is loaded
// routeConfiguration is an object that holds controller & url.
var routeConfiguration = $routeProvider.
figureOutRouteConfigurationFor(queryString);
var domTree = $(file('index.html'));
var $rootScope = $injector.get('$rootScope');
// find ng-view and clone it
var yourNgView = $($("attribute[ng-view='']").outerHTML);
// le's get rid of existing ng-view attribute
// and configure new ng-view with templateUrl & controller.
yourNgView.RemoveNgViewAttribute();
yourNgView.AddAttribute("ng-controller", routeConfiguration.controller);
yourNgView.AddAttribute("ng-view", routeConfiguration.templateUrl);
// compile the view & pass the rootScope.
$compile(yourNgView)($rootScope);
// replace the existing dom element with our compiled variant
$("attribute[ng-view='']").replaceHtmlWith(yourNgView);
// we can now cache the resulted html.
return cached[queryString] = domTree.html;
}
ngCloak
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
https://docs.angularjs.org/api/ng/directive/ngCloak
<body ng-cloak>
You should read this article http://sc5.io/posts/how-to-implement-loaders-for-an-angularjs-app

Conditional templates in AngularJs

I have a login page, which is just a simple form in the center of the page with no header, footer or sidebar. My regular pages will have a sidebar, header and footer (these 3 are directives). I call all my templates in ng-view, depending on the routes.
I want to place my directives outside ng-view since they'll be common across all the pages except login and I don't want them to be fetched on every URL change.
How do I have a login page without the 3 directives?
I created a small plunk to offer you a solution to your issue.
this is the base of the solution:
<ng-include src="appVm.templatetoShow">
</ng-include>
in the Appcontroller :
function AppController() {
this.templatetoShow = 'login.html';
}
I included a couple of templates that gets loaded when needed.
After some rereading, I made a new plunk to show you how this can be done.
The hiding part is simple, just add some CSS into the mix, and you won't see
anything from your main page.
Added some cloaking to prevent FOUC, and wrote a couple of lines to show
an authentication system.
I introduced an User service that you can inject wherever you need the users
data.
Is this more in what you wanted?
I dont know if I get what You really want to do, but i would suggest to use ng-if
such ng-if may look look like this:
<div ng-if="notLogin"> here is Your navbar contents. Directies, anything You need </div>
and then in controller -
$scope.notLogin = $location.path() !== "/login"
If You have directives for footers, navbars etc, i think You can do that in directive controller and hide all content of directive there, so directive will be empty.
You may also use global controller to do that job and wrap those directives in divs like in my example. Doing it in directives looks cleaner though.
Hope its what You need.
you can use ng-if to conditionally display/hide them on UI. Please note that ng-if is different than ng-switch. The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}. If the expression assigned to ngIf evaluates to a false value then the element is removed from the DOM.
More details - https://docs.angularjs.org/api/ng/directive/ngIf
Qucik Steps -
create a shell page and set the header, footer, sidebar and a view
use ng-if={{User.isLoggedin}} for header, footer, sidebar and login
set User.isLoggedin to false when user is not logged in
Set it to true once user loged in
Directives are shared across all views if you use the same controller however using different controllers is not adviced either. what makes you uncomfortable with that ?
have a look at this post:
https://stackoverflow.com/a/16213055/1218551
best wishes:)

issue with ui-router not working in the angular app

I am trying to get the URL router working for my app but for some reason it will not link the files.
I have created a simple plnkr to demonstrate the issue ...
can someone please assist
http://plnkr.co/edit/XQs2vP4vtqnBWGKqmJZz
Thanks
There are some misunderstandings:
You did not specify a ui-view to render to state into
You configured the states as named views, which makes only sense if you need more than one ui-view in parallel (nesting views is always possible)
How to fix it
Use <div ui-view></div> in your views for normal views and use <div ui-view="content"></div> for named views (content is a custom name which you may choose at will).
Also, you can use the ui-sref attribute to link to specific states which is better practice than setting the href manually. In all demos below i showed both ways.
Note that normal views have a simpler state definition syntax:
.state('person', {
url: '/persons',
templateUrl: 'list1.html',
controller: 'employeeCtrl'
})
Demos
Demo with normal views (for single views)
Demo with named views (for multi-views)
In both cases you can nest views as a further exploration of the possibilites of ui-router (which makes it way superior to default angular routing):
Demo with normal views and nesting
Demo with named views and nesting
Unlimited possibilities.

How can I programmatically/dynamically change the view?

I'd like to have a "wizard" of sorts where the screen changes a few times during the process. I'd like to utiilze Angular's controllers for this. I can't however, figure out how to change the view that's being displayed programmatically.
It doesn't look like there's any kind of $scope.setView('/path/to/my/view.htm') I can define.
You are going to want to learn about ngView => http://code.angularjs.org/1.1.4/docs/api/ng.directive:ngView
This way you can use $route to configure the display of partial content. You will want to declare the template option like:
$routeProvider.when('/path', {
templateUrl: '/path/to/my/view.htm',
}
Another option is to use ngSwitch => http://code.angularjs.org/1.1.4/docs/api/ng.directive:ngSwitch
For a wizard I would think that ngSwitch is less efficient but will be the easier of the two. This does sound like what you are looking for though.
"The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression."
document.location.href='#/yourRoute' to change the route programmatically
And if you want to use several views with 1 controller: define different routes with different views but the same controller

Router and refresh multiples ng-inludes

I start with code:
when('/admin', {
templateUrl: 'partials/admin/layout.html',
controller: AdminCtrl
})
when('/admin/products', {
templateUrl: '????',
controller: AdminProductsCtrl
})
Template "tree":
index.html ---> <div ng-view/>
---layout.html ---> <div ng-include=menu/> and <div ng-include=body/>
------menu.html
------products.html
Actually I do this:
function AdminCtrl($scope) {
$scope.menu = 'partials/admin/menu.html';
}
function AdminProductsCtrl($scope) {
$scope.menu = 'partials/admin/menu.html';
$scope.body = 'partials/admin/products/index.html';
}
The point is: What I put in '????', if I put layout.html this work fine, but I like just "refresh" ng-include=body. I think that my concepts about Angularjs is wrong.
Other problem is, when AdminProductsCtrl "take the control" of layout.html I miss the AdminCtrl $scope, this implicates repeat all AdminCtrl $scope in AdminProductsCtrl $scope (for example $scope.menu).
Thanks a lot, and sorry for "my english".
UPDATE
After think.. and think... I understanding that routes not apply for my app, then I manage all functionality under one url 'site.com/#/admin'. The menu.html is manage for AdminMenuCtrl, this controller contains a model for each 'ng-include' and contains one method for each menu entry. When the user click a menu entry, the associate method in the $scope replace $scope.includes.body with the 'new' html. The partial cointains your ng-controller.
This works fine by now :D. And the best is that I don't need use $rootScope.
The new problem is a bit more complicated, the ng-include require a tag (i.e DIV) and ng-controller too. Then my design is affected for this. In code language:
DESING:
<div>MENU-HTML</div>
<div>BODY-HTML</div>
TEMPLATE:
<div ng-include="menu"></div>
<div ng-include="body"></div>
AFTER RETRIEVE PARTIALS:
<div ng-include="menu"><div ng-controller="MenuCtrl">MENU-HTML</div></div>
<div ng-include="body"><div ng-controller="ListProductsCtrl">BODY-HTML</div></div>
THE IDEAL THING:
1 - ng-include don't 'include' into the DIV, instead 'replace' the DIV.
2 - ng-controller DIV is replaced for nothing in the DOM.
It's possible now with angular? Is a bad approach this idea? The point 2 with $route is possible, not with ng-controller directive.
I believe you are correct in your example you would set ???? to layout.html but the idea is to have different views based on the route so pointing to the same layout.html is not ideal.
If you are trying to keep a static menu on all pages I would add the menu to your index.html and then choose a different templateUrl for each route (ie /admin goes to partials/admin.html and /admin/products goes to partials/products.html) and not use the ngInclude.
I'm new to AngularJS but I'm getting the impression that you generally want to use ngView with routes to templateUrls OR use ngInclude (possibly with ngSwitch) if you want to roll your own view switching. I'm sure there are times when using both is appropriate but as a newbie it confuses me somewhat. Resident experts please correct me if I'm wrong!
For your second issue there might be some helpful information here and here for tips on sharing the same model across multiple controllers but you probably don't need to for your example.
An alternative is to use a string constant path to your partial in layout.html and remove the references to $scope.menu in your controller code by using:
<div ng-include="'partials/admin/menu.html'"/>

Resources