I'm using AngularDart and routing. My views are one-liners that contain a single component. I feel the views are not carrying their weight. Can I route directly to a component?
Example:
router.root
..addRoute(
name: 'welcome',
path: '/welcome',
defaultRoute: true,
enter: view('views/welcome.html'))
..addRoute(
name: 'camera',
path: '/camera',
enter: view('views/camera.html'));
And here's views/welcome.html
<welcome></welcome>
And here's views/camera.html
<camera></camera>
As you can see, those views are pretty weak. I'm much rather say: "when you enter a view, insert this component"
Is that possible?
This seems to be directly supported now
see https://github.com/angular/angular.dart/issues/425
You can route to an inline template (viewHtml):
views.configure({
'foo': ngRoute(
path: '/foo',
viewHtml: '<foo-component></foo-component>')
});
I wanted to ask the same question, but you were the first! I found a solution, probably not the best one, but at least it works.
I created one html for all routes:
dyn_view.html:
<div><dynamic-view></dynamic-view></div>
and of course a component dynamic-view, which takes actual component tag name from the route provider and dynamically adds to the DOM using the technique described here: How to add a component programatically in Angular.Dart?
dynamic_view.html
<div></div>
dynamic_view.dart
#NgComponent(
selector: 'dynamic-view',
templateUrl: 'view/dynamic_view.html'
)
class DynamicView implements NgShadowRootAware {
Compiler compiler;
Injector injector;
String elementName;
DynamicView(this.compiler, this.injector, RouteProvider provider) {
elementName = provider.parameters["elementName"];
}
void onShadowRoot(ShadowRoot shadowRoot) {
DivElement inner = shadowRoot.querySelector("div");
inner.appendHtml("<$elementName></$elementName>");
BlockFactory template = compiler(inner.nodes);
var block = template(injector);
inner.replaceWith(block.elements[0]);
}
}
Now the router. The element name must be passed somehow to the RouteProvider. I was inspired by this post: Verifying route preconditions prior to loading controller
class DynamicViewFactory {
ViewFactory viewFactory;
DynamicViewFactory(this.viewFactory);
call(String elementName) {
return (RouteEvent event) {
event.parameters["elementName"] = elementName;
viewFactory("view/dyn_view.html")(event);
};
}
}
class MyRouteInitializer implements RouteInitializer {
init(Router router, ViewFactory view) {
DynamicViewFactory dynView = new DynamicViewFactory(view);
router.root
..addRoute(
name: 'route1',
path: '/a/:b',
enter: dynView('componentA'))
..addRoute(
name: 'route2',
path: '/b/:c',
enter: dynView('componentB'));
}
}
The above code is slightly modified from what actually I use, so it can have some small bugs, but the general idea is the same.
Hope that helps!
Related
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"
I'm switching my app over to the new routing DSL. Specifically, I want to do something like this with preEnter:
final RouteInitializerFn routes =(Router router, ViewFactory views) {
views.configure({
'chat': ngRoute(
path: '/chat',
// authService.requireState returns a Future<bool>, and may invoke an HttpRequest
preEnter: (RoutePreEnterEvent e) => e.allowEnter(authService.requireState(LOGGED_IN)),
view: 'views/chat.html'),
'login': ngRoute(
path: '',
defaultRoute: true,
view: 'views/login.html')
});
}
This would be configured in the module as follows:
value(RouteInitializerFn, routes);
In case you missed it, I'm referencing an injectable authService within the RouteInitializerFn. This isn't possible since RouteInitializerFn is a function and not a class, so nothing can be injected into it. If I encapsulated the routes function within a class, I'm not sure how I could configure RouteInitializerFn, so I'm in a bit of a quandary.
I found a pretty cool solution to this problem. Turns out, if you define a call method on a class that satisfies a typedef, you can configure it as an implementation of the typedef. Very cool. Here is my solution:
class Routes
{
final UserService _userService;
Routes(this._userService);
void call(Router router, ViewFactory views)
{
views.configure({
'chat': ngRoute(
path: '/chat',
preEnter: (RoutePreEnterEvent e) => e.allowEnter(this._userService.requireUserState(UserService.LOGGED_IN)),
view: 'views/chat.html'
),
'login': ngRoute(
path: '',
defaultRoute: true,
view: 'views/login.html'
)
});
}
}
and this is how it's configured within the module:
// old syntax
type(RouteInitializerFn, implementedBy: Routes);
// new syntax
bind(RouteInitializerFn, toImplementation: Routes);
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
I am writing a simple web api application. I came to a phase when I need to have two POST methods in my web api controller. One of these methods works and the other does not. My route table looks like this:
config.Routes.MapHttpRoute(
name: "ApiRouteWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then I have my methods defined like this:
[HttpPost]
public bool PostTaskAgain(My3TasksWebAPI.Data.Task task)
{
var oldTask = _db.Task.Where(t => t.Id == task.Id).SingleOrDefault();
oldTask.DoAgain = true;
oldTask.DateUpdated = task.DateUpdated;
if (_db.SetOfTasks.Where(t => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(t.DateCreated, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday) == CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday)).Any())
{
int currentSetOfTasksId = _db.SetOfTasks.OrderBy(s => s.DateCreated).FirstOrDefault().Id;
My3TasksWebAPI.Data.Task newTask = new Data.Task() { CreatedBy = oldTask.CreatedBy, DateCreated = oldTask.DateCreated, DateUpdated = null, DoAgain = false, Notes = string.Empty, SetOfTasksId = currentSetOfTasksId, Status = false, Title = oldTask.Title, UserId = oldTask.UserId };
_db.Task.Add(newTask);
}
_db.SaveChanges();
return true;
}
// Post api/values/PostSetOfTasks/{setOfTasks}
[HttpPost]
public bool PostSetOfTasks(My3TasksWebAPI.Data.SetOfTasks setOfTasks)
{
_db.SetOfTasks.Add(setOfTasks);
_db.SaveChanges();
return true;
}
When I try to call PostTaskAgain I get an internal server error. I think that it might be the routing table but I am not sure how to handle two post methods.
I call the web api from my asp.net mvc application like this:
HttpResponseMessage response = client.PostAsJsonAsync("api/values/PostSetOfTasks", model.SetOfTasks).Result;
and
HttpResponseMessage response = client.PostAsJsonAsync("api/values/PostTaskAgain", taskToPost).Result;
That means that I include the actions.
Working with POST in webapi can be tricky though conincidently, your issue turned out to be trivial. However, for those who may stumble upon this page:
I will focus specifically on POST as dealing with GET is trivial. I don't think many would be searching around for resolving an issue with GET with webapis. Anyways..
If your question is - In MVC Web Api, how to-
- Use custom action method names other than the generic HTTP verbs?
- Perform multiple posts?
- Post multiple simple types?
- Post complex types via jQuery?
Then the following solutions may help:
First, to use Custom Action Methods in Web API, add a web api route as:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}");
}
And they you may create action methods like:
[HttpPost]
public string TestMethod([FromBody]string value)
{
return "Hello from http post web api controller: " + value;
}
Now, fire the following jQuery from your browser console
$.ajax({
type: 'POST',
url: 'http://localhost:33649/api/TestApi/TestMethod',
data: {'':'hello'},
contentType: 'application/x-www-form-urlencoded',
dataType: 'json',
success: function(data){ console.log(data) }
});
Second, to perform multiple posts,
It is simple, create multiple action methods and decorate with the [HttpPost] attrib.
Use the [ActionName("MyAction")] to assign custom names, etc.
Will come to jQuery in the fourth point below
Third,
First of all, posting multiple SIMPLE types in a single action is not possible and there is a special format to post a single simple type (except for passing the parameter in the query string or REST style).
This was the point that had me banging my head with Rest Clients and hunting around the web for almost 5 hours and eventually, the following URL helped me. Will still quote the contents for the link may turn dead!
Content-Type: application/x-www-form-urlencoded
in the request header and add a = before the JSON statement:
={"Name":"Turbo Tina","Email":"na#Turbo.Tina"}
http://forums.asp.net/t/1883467.aspx?The+received+value+is+null+when+I+try+to+Post+to+my+Web+Api
Anyway, let us get over that story. Moving on:
Fourth, posting complex types via jQuery, ofcourse, $.ajax() is going to promptly come in the role:
Let us say the action method accepts a Person object which had an id and a name. So, from javascript:
var person = { PersonId:1, Name:"James" }
$.ajax({
type: 'POST',
url: 'http://mydomain/api/TestApi/TestMethod',
data: JSON.stringify(person),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(data){ console.log(data) }
});
And the action will look like:
[HttpPost]
public string TestMethod(Person person)
{
return "Hello from http post web api controller: " + person.Name;
}
All of the above, worked for me!!
Cheers!
There was a problem with my LINQ query.
The response from the server was: {"$id":"1","Message":"An error has occurred.","ExceptionMessage":"LINQ to Entities does not recognize the method 'Int32 GetWeekOfYear(System.DateTime, System.Globalization.CalendarWeekRule, System.DayOfWeek)' method, and this method cannot be translated into a store expression.","ExceptionType":"System.NotSupportedException","StackTrace":" at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.DefaultTransl‌​ator.Translate(ExpressionConverter parent, MethodCall....
After correcting the linq query everything is working fine. Visual studio was fine about me doing the linq query wrong.