We have a large already existing MVC 5 ASP.NET 4.5.1 web application. The core concept since there are so many areas it covers, is that each page was it's own application. All the existing pages don't use anything besides JQuery, regular Javascript, and Handlebars templating.
Angular 2 seems very exciting, but I'm trying to figure out how exactly it will work with our philosophy. For example below is how our mvc routing serves up our separate apps currently...
Area/Controller1/Action1(App1)
Area/Controller1/Action2(App2)
Area/Controller2/Action1(App3)
etc.
Then we have separate API controllers to serve up our JSON data. From initial readings/learnings of Angular 2 I can't seem to wrap my head around how I would serve separate apps (since everything I can find always wants the index.html as the home, which makes sense if you really are making a SPA). Essentially, we're trying to continue development with multiple SPAs served up via this existing structure, not have to change older "legacy" apps on the site to use Angular until they are revisited individually, and figure out how to do the routing effectively.
Routing:
So an example in my head would be
Area/Controller/Action(App)/routeUrlstuff
I still want to be able to let them copy and paste that extended link and be brought back using the angular routing, instead of maybe just cutting off that URL and starting the angular app at it's starting point.
I don't really have any code to show, as I'm attempting to do a current project as a proof of concept that using Angular 2 from now on is viable for the application.
I recently worked on a project which had similar concerns. We did thought about implementing multiple SPAs but in the end decided to implement one SPA with multiple modules.
I think we can extend that solution for multiple SPAs as well. Let’s look at a simple use case:
You want to create 2 SPAs
UserApp containing 2 modules (AddUser and ManageUser)
ProductApp containing 2 modules (AddProduct and ManageProduct)
You have following MVC controller actions that you want to use for above SPAs:
myApp/user/add
myApp/user/manage
myApp/product/add
myApp/product/manage
MVC controller actions 1 and 2 are to be used with SPA UserApp, routing to AddUser and ManageUser modules. Similarly, controller actions 3 and 4 are to be used with SPA ProductApp, routing to AddProduct and ManageProduct modules.
Conceptually this looks like:
Multiple SPAs with multiple modules
Angular Bundling:
I would leave the typescript transpiling and bundling to Angular CLI. If your project gets complex and you feel that the angular cli can’t handle your bundling needs, you can always eject webpack configuration.
There is a really good blog from Yakov Fain that you can look for configuring cli to bundle multiple SPAs. Basically you will be configuring angular cli to output your SPAs to different dist folders. In our case, let’s assume that these will be:
userAppDist for UserApp SPA
productAppDist for ProductApp SPA
MVC Layouts:
To Load different SPAs on different pages, you will have to create different layouts or sub-layouts for each SPA.
Let’s Say: _userLayout.cshtml for UserApp
Inside _userLayout.cshtml, you will have to load your scripts from userAppDist folder
Something like this:
<main class="layout">
<base href="/">
</main>
#*Now load scripts from userAppDist*#
Similarly you will have to implement layout for the other SPA loading scripts from productAppDist. Let’s say _productLayout.cshtml
Routes:
To keep things simple you can match your server routes to angular SPA module routes. Otherwise, you will have to implement HashLocationStrategy on angular App.
Assuming you are going with the simple option, you have following views:
myApp/user/add --> AddUser.cshtml
myApp/user/manage --> ManageUser.cshtml
myApp/product/add --> AddProduct.cshtml
myApp/product/manage --> ManageProduct.cshtml
AddUser.chtml and ManageUser.cshtml will use _userLayout and will look like:
<user-app></user-app>
This is targeting UserApp SPA
AddProduct.cshtml and ManageProduct.cshtml will use _productLayout and will look like:
<product-app></product-app>
This is targeting ProductApp SPA
The MainAppComponent templates for these Apps will have
<router-outlet></router-outlet>
which will resolve to the angular module routes, based on the routes from the server. Now you have to match the routes in you angular Apps
Example for UserAppRoutingModule:
const routes: Routes = [
{ path: 'myApp/user/add', loadChildren: 'userApp/modules/add-user/add-user.module#AddUserModule' },
{ path: 'myApp/user/manage', loadChildren: 'userApp/modules/manage-user/manage-user.module#ManageUserModule' }
];
Similarly, you will have to define matching routes for the product pages in product App SPA.
Hope this helps.
Update: Configuration to avoid multiple route files
For above use case, there will be following controllers:
UserController with index action pointing to user/index.cshtml (using _userLayout) having following code:
<user-app></user-app>
ProductController with index action pointing to product/index.cshtml (using _productLayout) having following code:
<product-app></product-app>
You will also need to modify you routeConfig.cs to include following:
routes.MapRoute("user", "user/{*catchall}", new { controller = "User", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("product", "product/{*catchall}", new { controller = "Product", action = "Index", id = UrlParameter.Optional });
Above changes will force following behaviour:
Routes to myapp/user --> will go to user/index.cshtml
Also, any extended routes after myapp/user/blah/blah will still resolve to user/index.cstml
User/index.chtml spins up Angular User SPA and then angular routes will kick in. You will observe similar behavior from product route and product/index.cshtml as well.
Final route configuration on angular SPA user:
const routes: Routes = [
{ path: 'user', redirectTo: 'user/add' },
{ path: 'user/add', loadChildren: 'userApp/modules/add-user/add-user.module#AddUserModule' },
{ path: 'user/manage', loadChildren: 'userApp/modules/manage-user/manage-user.module#ManageUserModule' }
];
The first route is default for user app. This maps to the MVC route myapp/user. Rest of the routes are not required to match MVC routes. You will have to do similar configuration on product SPA as well.
From a solution architect perspective, I would recommend changes both on server side and client side.
To being with, lets say there are 3 apps
Area/Controller1/Action1(App1)
Area/Controller1/Action2(App2)
Area/Controller2/Action1(App3)
1. Identify SPA's
Develop angular SPA for main application. The view from the main application has links to Controller1. This should be a route in your angular SPA. However Controller1/Action1 should be a route not available in angular SPA. It needs to be requested directly from the server using html href links. This essentially means page reload.
2. Project Locations on Server
Once development is complete for different SPA's, generate the production build with compressed JS and index.html.
Place the different SPA under their respective folders. For example, /home/user/proj/Controller1/Action1 is a folder which has independent SPA
proj
-- Controller 1
---- Action 1
---- Action 2
-- Controller 2
---- Action 1
3. Webserver Configuration
Configure your web/app server. For example in nodejs. (IIS will have something similar)
# For Action SPA
location = /area/(.*)/(.*) {
root /home/user/proj/$1/$2;
}
# For main SPA
location = / {
root /home/user/proj;
}
where $1 will contain target Controller
and $2 will contain target Action
The index.html from /area/target Controller/target Action will be fetched and these Action are independent SPA of their own.
4. Summary
Different teams can work on Main, App1, App2 and App3 as independent deliverables with this approach.
The idea is to configure MVC routing to map all possible urls for each controller to the same corresponding views i.e.:
routes.MapRoute("app1", "app1/{*catchall}", new {controller = "App1", action = "Index", id = UrlParameter.Optional});
routes.MapRoute("app2", "app2/{*catchall}", new {controller = "App2", action = "Index", id = UrlParameter.Optional});
Each Index.cshtml will have its own script section pointing to corresponding angular app. So if you paste URL app2/angular/dashboard/page it will route too corresponding controller and view (App2/Index.cshtml), then when angular router kicks in it will open the rest part of URL /angular/dashboard/page page.
Then adjust base url and angular routing for each app, which it depends on your app structure.
You don't have to do the routing in Angular for the overall site. If you're already doing routing on the ASP.NET MVC side just keep it that way. Think of it as each page request is it's own angular application. That means each page has it's own ng-app in the body, and it includes it's own angular.js and whatever that app.js is as well. app.js will init angular and setup the controllers/components you use for that page only. Each page would do that. If you have multiple views within each app, then you can use angular routing for just those views for just that app. When a person clicks on a new app link, it uses ASP.NET server side routing to give the new page that will init angular, setup inter-page routing on client side for just that app, and setup controllers used by that app.
I am not sure if i got the whole image but will try to help by telling you how i do multiple SPAs with Angular.
In _Layout.chtml, you create the following
<body data-ng-app="appMain">
<main>
#RenderBody()
</main>
</body>
In Home.chtml with above layout set, you write the following
<script src="#Url.Content("~/Models/HomeModel.js")" type="text/javascript"/>
<script src="#Url.Content("~/ViewModels/HomeViewModel.js")" type="text/javascript"/>
<div data-ng-controller="HomeViewModel" ng-init="">
home view html content
</div>
In HomeController.cs you create the Index action that return the above
For routing and multiple modules, angular is very good on that. See another sample code below:
CreateAccount.chtml with above layout set.
<script src="#Url.Content("~/Models/AccountCreateModel.js")" type="text/javascript"/>
<script src="#Url.Content("~/ViewModels/AccountCreateViewModel.js")" type="text/javascript"/>
<div data-ng-app="accountCreate" data-ng-controller="AccountCreateViewModel">
html content
</div>
AccountCreateViewModel.js:
var accountCreateModule = angular.module('accountCreate', ['common'])
.config(function ($routeProvider, $locationProvider) {
$routeProvider.when(rootPath + 'Area/Controller/Action(App)/routeUrlstuff', { templateUrl: rootPath + 'Templates/redirectPage.html', controller: 'AccountCreateViewModel' });
$routeProvider.otherwise({ redirectTo: rootPath + 'Area/Controller/Action(App)' });
$locationProvider.html5Mode(true);
});
accountCreateModule.controller("AccountCreateViewModel", function ($scope, $window) {
// module scope code here
});
Each angular module has its own routing and business logic completely distinct from other modules. Each area, with its mvc actions (full page reload) and web api methods (JSON)) is now an SPA silo written in a very neat way.
Related
We are working on a nextjs app with i18n. Now nextjs adds the language code in url automatically like /{language-code}/subroute/my-page. But we need it to be like /subroute/{language-code}/my-page. Is it possible through some configuration?
I checked basePath at https://nextjs.org/docs/api-reference/next.config.js/basepath, but it adds a prefix to ALL pages in the app. We just need some pages.
Also there is a so-called middleware to add custom routing rules: https://nextjs.org/docs/advanced-features/middleware. Is it possible to use it to need our needs?
I'm looking to build a static Next.js Single Page App that has dynamic routes, for example: /posts/123. I'd like to do this to:
Be able to host the site anywhere (e.g. S3)
Not need to know the routes at build time (so our APIs can change whenever independently of the Frontend/without requiring a rebuild of the Frontend)
From what I can tell, this should work with:
next build && next export
fallback: true
But the docs suggest that's only for loading/intermediate states.
Can I use some combination of fallback pages/catchall dynamic routes/static generation to get a static single-page app with dynamic routes at runtime? I'm okay with faking the routes using nginx (e.g. /posts/123 -> /index.html).
Edit: the above paragraph doesn't seem possible. From the docs, when using a required catchall route, e.g. [...post].js):
fallback: true is not supported when using next export.
So the url for my angular web app is https://devnet/appName/ and when I click a button I want it to go to http://devnet.some.org/appName/page.aspx. now here's the part I need help with, the angular web app is on devnet, testnet and (you guess it) prodnet. depending upon which server the angular app is running from is where I want it to go and find the old app.
https://devnet/appName/route ->
http://devnet.some.org/appName/page.aspx
https://testnet/appName/route ->
http://testnet.some.org/appName/page.aspx
https://prodnet/appName/route ->
http://prodnet.some.org/appName/page.aspx
Notice the change in protocol, the servers are the same as is the appName, but the route and page.aspx will be different.
And yes this is a temporary thing until the angular app is completely complete
The best approach from my POV is to use environments, e.g.:
environment.ts // current one
environment.dev.ts
environment.test.ts
environment.prod.ts
Add corresponding configurations in angular.json (propagate "production" configuration).
Add oldUrl to the environment interface and provide different values for each particular environment.
Import environment to the component where you the redirect button is used and use environment.oldUrl property to construct a proper redirect URL.
Hello I have SPA application with angularjs and I dont know how to create component with template url.
My directory structure is:
SpaApp
- Scripts
- App
- test.component.js
- Controllers
- Views
- Home
- test.component.cshtml
test.component.js
app.component("appTest", {
templateUrl: 'test.component.cshtml', //what is the address here?
transclude: true,
controller: function AppTest() {}
})
I tried add url to Views/Home/test.component.cshtml and it was not working.
Then I tried add html template to same folder as is the component javascript and it is not working too.
Where is my problem? What is the address to html template for component? Thanks you
You can't use razor views directly as angular templates. Razor views are compiled by ASP.NET and not served directly as static files by the web server, while Angular templates are either loaded asynchronously over http or retrieved through angulars templatecache.
You have two options.
Don't use razor views (i.e. .cshtml-files) as angular templates. Use regular .html-files instead.
Reference the ASP.NET MVC action, instead of a static file. In your case (assuming default routing configurations) your templateUrl should be /Home/Test instead.
I recommend the first option and keep ASP.NET MVC razor views completely separate from angular.
I have an annoying problem with folder names and routes in ASP .NET Mvc 5,
This is the default routes I'm using:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Site", action = "Index", id = UrlParameter.Optional }
);
And I'm also using a custom view engine to map views to the root folder instead of ~/Views so I can have HTML/CSS/JS files organized in a way my team can handle.
The problem is: When I call /Backoffice/Index it goes normally and executes action Index in controller Backoffice, all fine, but when we call /Backoffice, I expected it to presume the action index (by the route configuration), but instead, IIS seems to believe I'm trying to access the folder /Backoffice and gives me a 404 error instead of executing Backoffice::Index().
How can I configure IIS to behave in the intended way in this case? Or, is it really the only best way to keep views in a specific folder?
I did some research and used info from the comments and answers here and got to learn something about how MVC routes work and how IIS handles that.
When we set
routes.RouteExistingFiles = true;
The MVC runtime will ignore every file in the folder and start using the routes and URIs solely to look for controllers and actions, this means by calling /SomeContent it will search for a controller named Somefolder instead of a folder. Then we can configure to serve static content by telling MVC runtime to ignore some specific URI formats:
routes.IgnoreRoute("SomeContent/CSS/{filename}.min.css");
routes.IgnoreRoute("SomeContent/JS/{filename}.min.js");
This causes MVC to ignore URIs that match this pattern and leave it for IIS to resolve what to do with it, then IIS will look out on Web.Config rather there are configurations set to serve this kind of static content and what handler to use and proceeed as usual.
Using this configuration can bring MVC to a whole new level of control where you explicitly define which URI patterns serve static content, everything else explicitly calls an action on a controller, all URIs get to be firstly processed by MVC runtime. I sure wish someone correct me in this last statement if I get it wrong.
MVC routes certain actions based on whether they exist on disk. If you have a folder /BackOffice at the root level, then this appears to be a complication that MVC is going to have issues working around (I knew files were directly routed if they existed; I didn't realize folders were something the framework checked too). Consider renaming the folder or the controller to something else so you don't have this naming conflict. That is a problem with "by convention" approaches....