I've been fighting with this for a while now and decided to write a post.
I'm building a simple Single Page Application using VS2017 on ASP.Net Core 5.0 and Angular 2 over a template taken from ASP.NET Core Template Pack. The app is supposed to manage a contact list database.
The idea I have in mind is that the default starting '/home' page should be displaying the list of contacts using HomeComponent. All routing works fine, but when app is getting started or whenever I'm trying to route to '/home', it keeps going to ASP Home view's Index.cshtml page instead of using Angular routing.
Any idea how to make it go through Angular at all times? I wanted to align the HomeComponent with '/home' route but instead it keeps going to ASP page which is only there to say 'Loading...' which I don't really need (I think).
I've tried a lots of different solution but I wasn't able to get anything to work. I might be missing something obvious here as I'm not too advanced on these grounds, so if you can keep it basic, please do ;-)
Here's my Configure method from Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index" });
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index",});
});
}
app.module.shared.ts:
import { NgModule } from '#angular/core';
import { RouterModule } from '#angular/router';
import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { DetailsComponent } from './components/details/details.component';
import { EditComponent } from './components/editContact/editContact.component';
import { NewContactComponent } from './components/newContact/newContact.component';
import { HomeComponent } from './components/home/home.component';
import { ContactServices } from './services/services';
export const sharedConfig: NgModule = {
bootstrap: [ AppComponent ],
declarations: [
AppComponent,
NavMenuComponent,
DetailsComponent,
EditComponent,
NewContactComponent,
HomeComponent
],
providers: [ContactServices],
imports: [
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'details', component: DetailsComponent },
{ path: 'new', component: NewContactComponent },
{ path: '**', redirectTo: 'home' }
])
]};
ASP's Home Index.cshtml:
#{
ViewData["Title"] = "Home Page";
}
<app>Loading...</app>
<script src="~/dist/vendor.js" asp-append-version="true"></script>
#section scripts {
<script src="~/dist/main-client.js" asp-append-version="true"></script>
}
Aaaand home.component.ts:
import { Component } from '#angular/core';
import { ContactServices } from '../../services/services';
import { Response } from '#angular/http';
#Component({
selector: 'home',
templateUrl: './home.component.html'
})
export class HomeComponent {
public ContactList = [];
public constructor(private conService: ContactServices) {
this.conService.getContactList()
.subscribe(
(data: Response) => (this.ContactList = data.json())
);
}
}
Thanks in advance guys! Any advice will be appreciated.
I think that what is troubling you is that you wish to have Single App and have Angular doing client routing and posting WEB.API calls back to .NET Core Web API.
So, once you are on some page like www.example/subpage, and you press F5 to reload the same, avoid being kicked back to the homepage.
The solution is to create two MVC routes.
The first route will deal with redirecting a call to Web.API and the second one will accept any Angular URL request, and just ignore everything and forward the call to the Home controller.
To achieve that you need:
1. In Views\Home\Index.cshtml include your Angular component tag (my is app-root)
2. In Startup.cs, just before "app.Run" add the following code
app.UseMvc(cfg => {
cfg.MapRoute(
"API",
"api/{controller=*}/{action=*}/{id?}");
cfg.MapRoute(
"Default", // Route name
"{*catchall}", // URL with parameters
new { controller = "Home", action = "Index" });
});
I took different approach and rebuilt my application using the tutorial found under this link.
It is a solution where you first create ASP.NET CORE Web App API interface and install Angular over it. A little bit of 'engineering', works directly off Visual Studio and takes little time to set up.
Is there a reason why you are using asp.net with angular? I HIGHLY suggest you using angular cli. Importing libraries and publishing is very difficult with angular + asp.net.
Using angular cli will also always be "angular"
Related
im using AspNet Core 2 to build an app, im having problems with sessions, can you please help?
exact error:
No service for type 'Microsoft.AspNetCore.Http.HttpContext' has been registered.
im using this as guide, but seems to be not enough.
in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddMvc().AddSessionStateTempDataProvider();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromMinutes(1);
options.Cookie.HttpOnly = true;
});//<<==
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseSession();//<<==
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
in home controller:
using Microsoft.AspNetCore.Http;
HttpContext.Session.SetString("TestName", user.UserName);
HttpContext.Session.SetString("TestVal", user.testVal.ToString());
to this point it seems to be working, im able to navigate through different pages and i can use/check sessions in controller side.
But the problem is when im trying to use sessions in any view
View side:
#using Microsoft.AspNetCore.Http
#inject HttpContext HttpContext
#if (#HttpContext.Session.GetString("testVal").Equals("True"))
{
<td><input type="checkbox"></td>
}
else
{
<td><input type="checkbox" readonly></td>
}
when i try to get or set sessions cshtml/view side i get the error
can you please help?
a temporary fix while i find a better solution is:
controller side
HttpContext.Session.SetString("TestName", user.UserName);
HttpContext.Session.SetString("TestVal", user.testVal.ToString());
HttpContext.Session.SetString("SecurityToken", securityToken);
ViewBag.TestVal = user.testVal.ToString();
view side
#if (ViewBag.TestVal.Equals("True"))
{
<td><input type="checkbox"></td>
}
else
{
<td><input type="checkbox" readonly></td>
}
where security token is a random long value created when the session start, this isnt the best way ... and you must verify this token each time you want to update,create or delete something, that plus AntiForgeryToken, could be the best for a non prod tests or a first aproach...
if you know a better way please share
i found the way to use sessions view side with asp net core 2
#using Microsoft.AspNetCore.Http
Session Value = #Context.Session.GetString("TestName")
Here
thank you all
I'm trying to integrate asp.net mvc with an angular 2 application. I understand that this is not ideal, but I am being asked to integrate some existing Mvc functionality (think big legacy app) into a brand new Angular 2 spa.
What I would like to be able to do is have a cshtml view that has angular components in it, as well as pure mvc stuff...
<side-bar></side-bar>
<action-bar></action-bar>
#{
Html.RenderPartial("_SuperLegacyPartialView");
}
I'm struggling to find any way to do this. This blog post looked promising - http://www.centare.com/tutorial-angular2-mvc-6-asp-net-5/. It used a templateUrl value that pointed to a path rendered by Mvc, as well as AsyncRoute, but none of that works anymore in Angular 2. This post looked promising as well - http://gbataille.github.io/2016/02/16/Angular2-Webpack-AsyncRoute.html, but it uses AsyncRoute too, which is deprecated.
This used to be very easy in Angular 1. We used to either manually bootstrap angular into a Razor View, or render a partial view as the templateUrl of a component/directive. What is the best way to do this in the latest Angular 2 that uses Webpack?
I came up with a solution that satisfied my needs at the time. I'm using angular-cli with WebPack, and this worked for my needs. I don't understand all the examples I've seen that say to use "templateUrl: '/Template/Index'", where the path is a path to an MVC view. That simply doesn't work because the path can't be found inside any of the bundled views that WebPack creates. Maybe those people aren't using angular-cli and WebPack.
This stackoverflow answer - How can I use/create dynamic template to compile dynamic Component with Angular 2.0? was very helpful in creating the following directive. This directive will take the output of an mvc partial view and compile it. It allows for Razor/server logic to take place, and some angular to be compiled as well. Although, actually including other components inside this MVC partial was problematic. If you get that working, please let me know what you did. In my case, I just needed the server rendering to happen and to place that exactly where I wanted it in my Angular 2 spa.
MvcPartialDirective
import {
Component,
Directive,
NgModule,
Input,
ViewContainerRef,
Compiler,
ComponentFactory,
ModuleWithComponentFactories,
ComponentRef,
ReflectiveInjector, OnInit, OnDestroy
} from '#angular/core';
import { RouterModule } from '#angular/router';
import { CommonModule } from '#angular/common';
import {Http} from "#angular/http";
import 'rxjs/add/operator/map';
export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);
#NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] })
class DynamicHtmlModule { }
return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
});
}
#Directive({ selector: 'mvc-partial' })
export class MvcPartialDirective implements OnInit, OnDestroy {
html: string = '<p></p>';
#Input() url: string;
cmpRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: Http) { }
ngOnInit() {
this.http.get(this.url)
.map(res => res.text())
.subscribe(
(html) => {
this.html = html;
if (!html) return;
if(this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: this.html,
});
createComponentFactory(this.compiler, compMetadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
});
},
err => console.log(err),
() => console.log('MvcPartial complete')
);
}
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
in some-component.html (assuming your mvc app shares the domain with your spa)
<mvc-partial [url]="'/stuffs/mvcstuff'"></mvc-partial>
MvcStuff.cshtml
#{
ViewBag.Title = "This is some MVC stuff!!!";
}
<div>
<h2>MVC Stuff:</h2>
<h4>#ViewBag.Title</h4>
<h2>Angular Stuff:</h2>
<h4>{{1 + 1}}</h4>
</div>
in StuffsController.cs
public PartialViewResult MvcStuff() => PartialView();
I did it like this.
#Component({
templateUrl: '/Template/Index'
})
export class TemplateComponent {}
"/Template/Index" is the URL in your MVC Controller, and then the method.
public IActionResult Index()
{
return PartialView();
}
My problem is i don't know how the refresh the view to call controller method every time is loaded.
For those who are on Angular 7, you will need to change the accepted answer a little bit to make it work.
In MvcPartialDirective:
Update Http to HttpClient so that it reads:
import { HttpClient } from '#angular/common/http';
In ngOnInit(), specify the responseType:
this.http
.get(this.url, {responseType: "text"})...
Update to pipe:
.pipe(map(res => res.toString())) (note toString() insteadd of .text())
Optionally is to use app prefix to directive specification:
#Directive({
selector: 'appActionResult'
})
End result:
import {
Component,
Directive,
NgModule,
Input,
ViewContainerRef,
Compiler,
ComponentFactory,
ModuleWithComponentFactories,
ComponentRef,
ReflectiveInjector, OnInit, OnDestroy
} from '#angular/core';
import { RouterModule } from '#angular/router';
import { CommonModule } from '#angular/common';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent { };
const decoratedCmp = Component(metadata)(cmpClass);
#NgModule({
imports: [CommonModule, RouterModule],
declarations: [decoratedCmp],
schemas: [NO_ERRORS_SCHEMA] })
class DynamicHtmlModule { }
return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
});
}
#Directive({
selector: 'appActionResult'
})
export class ActionResultDirective implements OnInit, OnDestroy {
html = '<p></p>';
#Input() url: string;
cmpRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler, private http: HttpClient) {}
ngOnInit() {
this.http
.get(this.url, {responseType: "text"})
.pipe(map(res => res.toString()))
.subscribe(
(html) => {
this.html = html;
if (!html) { return; }
if (this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: this.html,
});
createComponentFactory(this.compiler, compMetadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
});
},
err => console.log(err),
() => console.log('MvcPartial complete')
);
}
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
I needed to use MVC PartialView html in my angular 4 application, called by the HttpClient .get method.
I used AMD's post
to convert my partial view to an html string. I returned this in a container json object, and set it into a variable that set the html of a div on my page..thus:
...in the template
<div class="files" [innerHtml]="myTemplate">
</div>
... in the component .ts file
export interface htmldata {
html: string;
}
... inside component
getDivHtml(path: string): Promise<htmldata> {
return this.http
.get<htmldata>(`${this.baseUrl}/MVC/Index?path=` + path , { withCredentials: true })
.toPromise();
}
ngOnInit() {
this.getDivHtml('').then(
data => { this.loadData(data); },
).catch( error => { console.log(error); });
}
loadData(data: htmldata) {
this.myTemplate = data.html;
}
...on server
public class HtmlReturn
{
public string html { get; set; }
}
[Produces("application/json")]
[Route("api/MVC/[action]")]
public class MVCController : Controller
{
private readonly ViewRender view;
public MVCController(ViewRender view)
{
this.view = view;
}
public IActionResult Index(string path)
{
data.html = this.view.Render("viewpath", viewModel);
return Json(data);
}
}
Please note: this only works well with static html that doesn't need event listeners. I was not able to add click events to the loaded html with renderer2, although I am not an expert and it may be possible.
You will need to create the ViewRender class and add an injection instruction into the startup.cs file as shown in AMDs post
Using Systemjs:
https://github.com/VahidN/MVC5Angular2
Using Webpack:
http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/
I'm going to be crazy with routing, since angular2 RC1 i cannot route correctly, the last error i have is :
EXCEPTION: Error: Uncaught (in promise): Component 'LoginForm' does
not have route configuration
My code is bellow (i hoppe the usefull parts)
I cannot understand the message of error, what i want is just some links available on my main page using the '[routerLink]' routing to the correct components (my main activities)
Regards
Note : "/app/" is just a base path for my app urls (it does not correspond to a component)
bootstrap(AppComponent,[
ROUTER_PROVIDERS
]);
/////////////////////
#Component({
selector: 'my-app',
template: `
<navbartop></navbartop>
<h1>MAIN APP COMP</h1>
<a [routerLink]="['/app/coursesactivity']">CouresesActivity</a>
<router-outlet></router-outlet>
`,
directives: [ROUTER_DIRECTIVES, NavBarTop],
providers: [ROUTER_PROVIDERS]
})
#Routes([
{ path: '/app/login', component: LoginForm },
{ path: '/app', component: LoginForm }
])
export class AppComponent implements OnInit, OnActivate {
.....
}
/////////////////////
#Component({
selector: 'loginform',
templateUrl: '/app/components/compLoginForm/LoginForm.component.html',
styles: ['/app/components/compLoginForm/LoginForm.component.css']
})
export class LoginForm implements OnInit {//, OnActivate {
constructor(
//private _router: Router
//private _routeData: RouteData, deprecated
//private _routeParams: RouteParams deprecated
) {
}
...
}
I decided to cleanup the code, i encountered other errors,
finally i don't know where the original error came from...
Sorry for thos who helped me.
Regards
I have a basic Angular Dart program which currently allows logging in and shows a basic dashboard when logged in. What I would like to do is redirect to the dashboard route after a successful login. I do not know how to access the router object from within the login controller, attempts to use DI to load in Router to the controller work but give me a fresh Router object instead of the previously initialised one (as expected).
main.dart
import 'package:angular/angular.dart';
import 'dart:convert' show JSON;
import 'dart:html';
class TTRouter implements RouteInitializer {
Cookies _cookies;
TTRouter(this._cookies);
init(Router router, ViewFactory view) {
router.root
..addRoute(
name: 'login',
path: '/login',
enter: view('login.partial.html'))
..addRoute(
name: 'home',
path: '/dashboard',
enter: view('dashboard.partial.html'));
}
}
#NgController(
selector: '[login-controller]',
publishAs: 'ctrl')
class LoginController {
Http _http;
Scope _scope;
LoginController(this._scope, this._http);
login() {
// Login API request ommitted
// TODO: insert redirect to 'home' route here
}
}
class TTModule extends Module {
TTModule() {
type(RouteInitializer, implementedBy: TTRouter);
type(LoginController);
factory(NgRoutingUsePushState,
(_) => new NgRoutingUsePushState.value(false));
}
}
main() => ngBootstrap(module: new TTModule());
login() is called using ng-submit="ctrl.login() from the login partial view.
I would be grateful for any comments on the structure of the code as well if I'm approaching this the wrong way. I am new to both Dart and Angular (read/watched tutorials but this is the first app I am building on my own).
If you add the router as value to the module instead of type you get the same instance every time.
TTModule() {
value(RouteInitializer, new TTRouter());
}
try with NgRoutingHelper.
class LoginController {
Http _http;
Scope _scope;
NgRoutingHelper locationService;
LoginController(this._scope, this._http, NgRoutingHelper this.locationService );
login() {
// Login API request ommitted
// TODO: insert redirect to 'home' route here
locationService.router.go('home', {} );
}
}
don't forget to add the service in your module
type(NgRoutingHelper );
You should be always getting the same instance of the Router -- there can only be one, otherwise unpredictable things will start happening.
class LoginController {
Http _http;
Scope _scope;
Router _router;
LoginController(this._scope, this._http, this._router);
login() {
_router.go('home', {} );
}
}
This is my code,
import 'package:angular/angular.dart';
class AppModule extends Module {
AppModule(){
type(AppController);
type(LoginController);
type(RouteInitializer, implementedBy: AppRouter);
}
}
class AppRouter implements RouteInitializer {
init(Router router, ViewFactory view) {
router.root
..addRoute(
name: 'login',
path: '/login',
enter: view('app/views/login.tpl.html'))
..addRoute(
defaultRoute: true,
name: 'index',
enter: view('app/views/index.tpl.html'));
}
}
#NgController(selector: '[app-ctrl]', publishAs: 'ctrl')
class AppController {
}
#NgController(selector: '[login-ctrl]', publishAs: 'ctrl')
class LoginController {
Http _http;
String works = 'Works.';
LoginController(this._http);
}
No routes are working, clicking on a '#/login' link does not change the url or the view.
Log says
clicked /app/web/index.html#/login
route /app/web/index.html [Route: null]
route [Route: index]
What am I doing wrong?
There might be a couple of problems with this code. From what I can see the most likely problem is that the routing is using pushState. When you use pushState you don't manipulate the url using a hash. For more information on that see:
Manipulating Browser History
Angular will use push state when a browser supports it.
bind(NgRoutingUsePushState, toValue: new NgRoutingUsePushState.value(false));
Giving you are module of:
class AppModule extends Module {
AppModule(){
bind(AppController);
bind(LoginController);
bind(RouteInitializer, implementedBy: AppRouter);
bind(NgRoutingUsePushState, toValue: new NgRoutingUsePushState.value(false));
}
}
Other possible problems include:
Not having an ng-view directive
Not setting the ng-bind-route in app/views/login.tpl.html, and app/views/index.tpl.html
When I made all these changes your code worked correctly for me when navigating to #/login