Creating a single page application using asp.net MVC4 and AngularJs - angularjs

Hi, I am trying to create a single page application using AngularJs
and Asp.net MVC4. To start off, I tried to do basic navigation using
angular. I followed below steps.
Create MVC4 web application, Selected internet application,
Downloaded AngularJs libraries from Nuget,
Referenced it in Bundles.config file Gave reference of Angular in my
_layout.cshtml page,
Wrote app.js file for routing
Below is the code.
This is my bundles.config file
bundles.Add(new ScriptBundle("~/bundles/angular").Include(
"~/Scripts/angular.js",
"~/Scripts/angular.min.js",
"~/Scripts/angular-route.js",
"~/Scripts/angular-animate.js",
"~/Scripts/app/app.js",
"~/Scripts/app/js/controllers.js"));
This is my _Layout.cshtml file. As you can see I have used ng-app
directive to let html understand about angular.
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body ng-app>
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">#Html.ActionLink("your logo here", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
#Html.Partial("_LoginPartial")
</section>
<div class="nav-collapse">
<nav>
<ul class="nav">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>Contact</li>
</ul>
</nav>
</div>
#*<div ng-view></div>*#
</div>
</div>
</header>
<div id="body">
#RenderSection("featured", required: false)
<section class="content-wrapper main-content clear-fix">
#RenderBody()
</section>
<div ng-view></div>
</div>
<footer>
<div class="content-wrapper">
<div class="float-left">
<p>© #DateTime.Now.Year - My ASP.NET MVC Application</p>
</div>
</div>
</footer>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/angular")
#RenderSection("scripts", required: false)
<script src="~/app/app.js"></script>
<script src="~/app/js/controllers.js"></script>
</body>
</html>
This is my Index.cshtml file
#{ViewBag.Title = "Home Page";}
<h1>Angular Tutorial</h1>
<p>This is an online searchable database for food and nutrition information.</p>
<h3>We suggest the following:</h3>
I did routing in app.js file which is as follows
'use strict';
angular.module('myApp', [
'myApp.controllers',
'ngRoute'
]).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/Home/Contact', { templateUrl: 'Views/Home/Angular.html', controller: 'MyCtrl1' });
$routeProvider.otherwise({ redirectTo: '/Home/About' });
}]);
And last this is my controller. I have written an alert message to
check if angular is working.
'use strict';
angular.module('myApp.controllers', [])
.controller('MyCtrl1', function ($scope) {
alert('Hello from partial One');
});
I don't know what is it that I am doing wrong. I tried several
tutorials but could not make it SPA. Please help me. I really want to
solve this now. This is getting on my nerves now. Thanks a lot in
advance.

Using Angular and MVC is actually using two MV* frameworks at the same time.
My suggestion is to pick one and use it. Don't try to use both at the same time as it will just conflict with each other and get you more confused.
Since you're wanting to create a SPA, then your choice should be Angular. ASP.NET/MVC4 isn't going to give your a SPA, but it can give you a full-featured website.
Plan out what you want first, then pick the technology to get yourself there. Don't pick the technology first.

You're including angular.js and angular.min.js. That could be the issue...nothing else immediately jumps out at me.

I see a couple of problems:
your including angular.js and angular.min.js in the same bundle:
The bundle minimizes the js files automatically when your not in "optimization" or "debug" mode, so dont reference them at all.
Your using multiple ng-app tags and I didn't really see a good reason for that:
Each application has one ng-app="".
Your mixing up two different concepts(asp.net razor views + angular js):
You need to do this smart and gently. If you want to gain razor's strength for using html helpers\bundles etc. then that's ok only if your sure you know what your doing. How are you going to bind data to your models? I think that you should change you concept and work with asp.net web api instead and use angular "the right way" without mixing stuff up.

If you don't need anything more than a basic page with some Angular, in Visual Studio, just create an 'ASP.NET Empty Web Application' then add the Angular packages you need, an html file and a JavaScript file and off you go. I did this recently while learning angular and made a simple image carousel with basic navigation. If it helps you out at all, I have GitHub repo with my solution:
git clone https://github.com/antonosmond/AngularCarousel
It should give you an idea of how to get started with Angular JS and Visual Studio, without all the complexities of trying to integrate Angular into an MVC app.

Related

Simple adding of Angular to MVC not working?

I'm going to make a simple MVC project that uses Angular as well. I think I have everything setup correctly but obviously I'm missing something that is probably simple.
When I run the site and look at the developer tools it looks as though jQuery is loaded before Angular. I don't know why that is or how to force it to load first.
I'm using VS 2015 and created a simple MVC with Web API app. I added the Angular.js v1.5 to my scripts folder, added the ng-app to the body, and added the angular.js to the bundle config file. I did rename the bundle but I don't think that's the issue.
Here is my BundleConfig. I added the angular file to the jquery bundle and renamed it to "tools". I made sure angular came before jquery.
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/tools").Include(
"~/Scripts/angular.min.js",
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*"));
// Use the development version of Modernizr to develop with and learn from. Then, when you're
// ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js"));
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
"~/Content/site.css"));
}
Here is my _Layout page. I changed the name of the bundle in the footer to "tools" to match my bundle. I also added ng-app to my body tag.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#ViewBag.Title - CPP Customer Call</title>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body data-ng-app="cppApp">
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
#Html.ActionLink("CPP Customer Call", "Index", "Home", new { area = "" }, new { #class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
#Html.Partial("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
#RenderBody()
<hr />
<footer>
<p>© 2016 - #DateTime.Now.Year</p>
</footer>
</div>
#Scripts.Render("~/bundles/tools")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
</body>
</html>
For now I'm just trying to write out a simple expression.
<h2>Angular {{ 1 + 1 }}</h2>
Viewing the page only shows "Angular {{ 1 + 1 }}" and not "Angular 2".
So the issue was that I applied an app name to the ng-app but didn't create a module for it first. This is how I fixed it. Hopefully it helps out someone else.
Create an app.js file. I put it in my Scripts/App/app.js structure.
(function () {
var app = angular.module('cppApp', []); //('moduleName', [array of injected modules])
}());
and updated my BundleConfig.cs to this
bundles.Add(new ScriptBundle("~/bundles/tools").Include(
"~/Scripts/angular.min.js",
"~/Scripts/App/app.js",
"~/Scripts/jquery-{version}.js"));

How to hide and show templates rather than destroy and reinject them everytime

I am using ui-router and Named views. it works flawlessly but it doesn't suit my purpose. I don't want the templates to be injected everytime i click on the menu item but only the first time it is clicked after application is loaded. The remaining times, it should simply hide the template and show the template that is currently active for a menu click. when coming back to the original menu click, it should just do ngShow="true" for the hidden template and hide the current one. Is that possible to do without editing the cleanupLastView() in the angular-ui-router?
If i comment out the cleanupLastView(), it simply keeps on injecting templates and does nothing to older templates. So both end up showing. Tried to debug the cleanupLastView() but i couldn't figure out what variables are available to me there so i could re-show what ever is clicked a second time rather than inject again.
index.html
<!DOCTYPE html>
<html lang="en">
<head ng-app="myApp">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="style.css" rel="stylesheet">
<script type="text/javascript" src=".lib/angular-1.3.0/angular.js"></script>
<script type="text/javascript" src="./angular-ui-router.js"></script>
<script type="text/javascript" src="./modular/ct-ui-router-extras.core.js"></script>
<script type="text/javascript" src="./modular/ct-ui-router-extras.dsr.js"></script>
<script type="text/javascript" src="../timerService.js"></script>
<script type="text/javascript" src="../dataService.js"></script>
<script type="text/javascript" src="app.js"></script>
</head>
<body>
<div class="navbar navbar-default navbar-static-top" role="navigation">
<ul class="nav navbar-nav">
<li ng-class="{active: $state.includes('main')}"><a ui-sref="home">Main</a></li>
<li ng-class="{active: $state.includes('files')}"><a ui-sref="files">Files</a></li>
</ul>
</div>
<div>
<div ui-view="pane"></div>
</div>
</div>
</body>
</html>
app.js
var myApp = angular.module("myApp", [ 'ui.router', 'ct.ui.router.extras.dsr']);
app.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('main',
url:"/main",
deepStateRedirect: true,
views : {
pane: {
templateUrl : 'tpl.pane-0.html'
},
}
)
.state('files',
url: '/files',
deepStateRedirect: true,
views : {
pane : {
templateUrl : 'tpl.pane-1.html'
},
'first#files' : {
templateUrl : 'tpl1.first.html',
deepStateRedirect: true
},
'second#files' : {
templateUrl : 'tpl1.second.html',
deepStateRedirect: true
},
},
)
});
tpl.pane-0.html
<div>
<input type='text'></input>
</div>
tpl.pane-1.html
<div >
<div data-ui-href='first'></div>
<div data-ui-href='second'></div>
</div>
tpl1.first.html
<div>
<input type='text'></input>
</div>
tpl1.second.html
<div>
Second
</div>
This is the simplified code of my app. it works to do the multiple named views but fails at deepStateRedirect which seems to be the one associated with nav bar type of templates rather than(?) Sticky. Is that correct? I added the param deepStateRedirect: true to allthe views being injected but to no avail. if the tpl1.first.html's input text box is written and the view switched to main, and come back, there is no text to be found in the input box. What am i doing wrong?
Yes, you can do this but it requires some custom work.
Change the state so that it uses an empty template.
In the state's controller check if the document already contains the HTML for the view (use a unique ID).
If required, use the $template cache and $compile to create the original template view and append it to the document body.
In the template, you can use ng-show="isState_expression | isState" expression to toggle the visibility of the template.
Basically the ui-routes work the same as before, but you're handling the compiling of the template yourself. Once it's in the DOM (not inside the ui-view). You can just leave it there to be handled by ng-show.
I don't recommend using resolve on the state since it's going to become a static state.
Here's the manual on $compile:
https://docs.angularjs.org/api/ng/service/$compile
Here's a tutorial on $compile:
http://www.benlesh.com/2013/08/angular-compile-how-it-works-how-to-use.html
Here's a stack answer on $compile:
Compiling dynamic HTML strings from database
Why doesn't it suit your needs? Do you need to preserve the states when they are not shown?
If so, perhaps you should look into UI Router Extras Sticky States. It allows you to preserve the $scope of a state and not create/destroy it every time. This would be better than trying to reinvent the wheel.
Take a look at the sample.

Load only body on page change

On my website i'm displaying the same header on each page and I wanted to know if there's an AngularJS / jQuery or simple JS solution to load only the content of the body and not the header on page change.
<html ng-app="headerApp" ng-controller="HeaderCtrl">
<head>
<!-- here I load my JS and css ... -->
<div ng-include="'templates/Header.tpl.html'"></div>
</head>
<body>
<div ng-include="'templates/IndexBody.tpl.html'"></div>
</body>
<footer><div ng-include="'templates/Footer.tpl.html'"></div> </footer>
</html>
So my HTML looks like this I have separate template for each parts. But for now I create a html file for each pages. So I think there's a way to change the ng-include in the body.
Thanks for your help !
This is kind of the idea behind single page applications. Angular provides a built-in router that does this for you, and there is also the popular ui-router plugin.
You would change your view to:
<html ng-app="headerApp">
<head ng-controller="HeaderCtrl">
<div ng-include="'templates/Header.tpl.html'"></div>
</head>
<body>
<div ng-view></div>
</body>
<footer><div ng-include="'templates/Footer.tpl.html'"></div> </footer>
</html>
and configure the router in app.js:
angular.module('headerApp', ['ngRoute']).config(function($routeProvider){
$routeProvider.when('/', {
templateUrl: 'templates/IndexBody.tpl.html',
controller: 'IndexBodyCtrl'
});
});
Note that you will need to include angular-route.js in your index.html. More reading here: https://docs.angularjs.org/api/ngRoute
If it's an Angular app would you not use ng-view? Everything outside the view is the template and static as such. If you aren't building a spa then Angular probably isn't the best approach.
If it's Jquery then you could just do:
$("article").load('templatefiletoload.html');
beware that loading in your content like this is poor from an SEO point of view. Use server side includes if possible

Can I bind an AngularJS model to a hash URL and an HTML input at the same time?

I'm teaching myself Angular and I've looked over a number of examples that show how to bind a model to an HTML input so that they always contain the same text.
I understand that Angular also provides the $location service which works with the URL.
I have an application that I'm thinking of partially rewriting in Angular as a learning example.
In my example, I have an HTML input that I keep synced up with a model using jQuery and also synced up with a hash URL.
Is there a simple way of accomplishing this with AngularJS?
Consider the example application bellow:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="Scripts/angular.js"></script>
<script>
var myApp = angular.module('myApp', []);
function FirstController($scope, $location) {
var data = {
bar: 'hello world'
};
$scope.data = data
}
</script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="FirstController">
<input ng-model="data.bar" />
<h2>{{ data.bar }}</h2>
</div>
</div>
</body>
</html>
This is a simple example showing how the model can be kept synced with a textbox. I was wondering if it's possible to keep it synced with a hash URL, as well, so that we would have http://www.example.com#bar=What_The_User_Typed
What you probably need is the $routeProvider
https://docs.angularjs.org/tutorial/step_07

AngularJS writing an app with no server

I'm trying to write an AngularJS client side only app.
I thought I might be able to load it from chrome by typing in the address bar:
file:///C:/path/to/project//index.html
I also tried to invoke chrome with the flag --allow-file-access-from-files
Unfortunatly nothing happened - just the busy sign on the tab name is working.
Why does is not loading my app?
I'm using the following code:
index.html:
<!doctype html>
<html ng-app="app">
<head>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
<script src="app.js"></script>
<title></title>
</head>
<body style="background-color: white;">
<h1>Index</h1>
<a id="link" href="/login">Go to login</a>
<ng-view></ng-view>
</body>
</html>
app.js:
angular.module('app', ['ngRoute']).
config(function($routeProvider) {
$routeProvider.
when('/', {controller: HomeCtrl, templateUrl: 'app.html'}).
when('/login', {controller: LoginCtrl, templateUrl: 'login.html', resolve: function() {}}).
otherwise({redirectTo:'/'});
});
function HomeCtrl($scope) {
$scope.numbers = [1,2,3,4,5];
}
function LoginCtrl($scope) {
}
app.html:
<div ng-controller="HomeCtrl">
<ul ng-repeat="number in numbers" >
<li>{{number}}</li>
</ul>
</div>
Edit:
2 possible solutions:
Close all chrome instances and run from command line: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files --allow-file-access
Run Firefox which does not have this restriction (Like #GeekBoy mentioned)
As far as I know google chrome does not allow javascripts to be run from file system. But I did a quick google search and found this. Might be useful
Link
On the flipside you can use firefox. Firefox doesn't have such restrictions as far as I know
Try changing the following lines:
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
to
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
I think since you're using a file-based way to get at index.html, it's assuming the // also points to a local system resource. By specifically indicating http://, it will look at the actual locations.
I know that this is an old question but for anyone that might still be interested, here is a small project that demonstrates how to write an angularjs client-side app. It is a complete AngularJS 1.63 single page application with routing that does not need a web server: https://github.com/jlinoff/aspa-nows.
There were two key challenges to getting it working: getting the the page href references right because of the newly introduced default hash prefix: ! (see aa077e8 for details), and embedding the template HTML code into index.html as ng-template scripts to avoid CORS errors. Once those fixes were made, it worked as expected.
The project README.md explains what needed to be done in detail and the full source code is available.

Resources