I am a beginner in angular2.
I try to use routes in a CRUD app. My problem are the nested routes. Chart example :
AppComponent
/ \
MealListComponent DishListComponent
\
DishEditComponent <--- Must have DishList template
The link / and \ respresent routes.
Problem : I want my DishEditComponent template is not include on DishListComponent template.
You can test app on http://plnkr.co/edit/g7NaoVd5BkGtSmr8ZkFW?p=preview go to Liste Dish link, then to Add dish link.
You'll see both Dish List title and Dish Edit orr Add title because DishEditComponent template is included in DishListComponent template by router-outlet tag, but I want that only Dish Edit or Add title displayed.
Do you know a way to avoid nested routes ?
You can try using asyncRoute.
Here is explanation for it.
import {Component, View, bootstrap} from 'angular2/angular2';
import {RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from 'angular2/router';
import {Home} from './components/home/home';
import {About} from './components/about/about';
#Component({
selector: 'app'
})
#RouteConfig([
{ path: '/', component: Home, name: 'home' },
{ path: '/about', component: About, name: 'about' }
])
#View({
templateUrl: './app.html',
styleUrls: ['./app.css'],
directives: [ROUTER_DIRECTIVES]
})
class App {}
bootstrap(App, [ROUTER_PROVIDERS]);
Here’s the implementation of the About component:
import {Component, View, CORE_DIRECTIVES} from 'angular2/angular2';
import {NameList} from '../../services/NameList';
#Component({
selector: 'about',
providers: [NameList],
templateUrl: './components/about/about.html',
directives: [CORE_DIRECTIVES]
})
export class About {
constructor(public list: NameList) {}
addName(newname):boolean {
this.list.add(newname.value);
newname.value = '';
return false;
}
}
The class, which implements RouteDefinition and allows asynchronous loading of the component associated with given route. This allows on demand loading of the component’s dependencies as well. Here’s now our definition will look like with AsyncRoute:
#RouteConfig([
{ path: '/', component: Home, name: 'home' },
new AsyncRoute({
path: '/about',
loader: () => System.import('./components/about/about').then(m => m.About),
name: 'about'
})
])
Basically we register two routes: - A regular route - Async route. The async route accepts as argument a loader. The loader is a function that must return a promise, which needs to be resolved with the component that needs to be rendered.
I found the solution.
1. I must remove this line in DishListComponent template :
<router-outlet></router-outlet>
2. Replace line :
<a [routerLink]="['DishEdit']">Add dish</a>
by this line :
<button (click)="addDish()">Add dish</button>
3. Add import :
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, Router } from '#angular/router-deprecated';
4. Update DishListComponent constructor :
constructor(private router: Router) {
}
5. Add this method in DishListComponent :
addDish() {
let link = ['DishEdit', {}];
this.router.navigate(link);
}
6. Remove PROVIDERS in DishListComponent
Final code
Final DishListComponent
#Component({
selector: 'dish-list',
directives: [ROUTER_DIRECTIVES],
template:`
<h1>Dish List</h1>
<button (click)="addDish()">Add dish</button>
<main>
</main>
`
})
export class DishListComponent {
constructor(private router: Router) {
}
addDish() {
let link = ['DishEdit', {}];
this.router.navigate(link);
}
}
The final RouteConfig
#RouteConfig([
{
path: '/dish-list',
name: 'DishList',
component: DishListComponent
//useAsDefault: true
},
{
path: '/dish-edit',
name: 'DishEdit',
component: DishEditComponent
},
{
path: '/meal-list',
name: 'MealList',
component: MealListComponent
}
])
The plunker link : http://plnkr.co/edit/LsLdc0efJtPaEbASWPek?p=preview
I hope it will help !
Related
How to change data in one component relatively to others on route event in Angular?
For e.g. if I have three components: "nav.component", "about.component" and "service.component".
So I want to display different text in "nav.component" when I switch between about and service pages in my app.
My "app.router.ts" file:
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { AppComponent } from './app.component';
import { AboutComponent } from './about/about.component';
import { ServiceComponent } from './service/service.component';
export const router: Routes = [
{ path: '', redirectTo: 'about', pathMatch: 'full' },
{ path: 'about', component: AboutComponent },
{ path: 'service', component: ServiceComponent }
];
export const routes: ModuleWithProviders = RouterModule.forRoot(router);
I don't want to display just page name text in my nav bar while switching between these pages, it would be a custom text for each component.
Also I would like to store this data/text directly in "about.component.ts" and "service.component.ts" but not in the "app.router.ts" due to maintainability and scalability.
Is it possible?
U.P.D.
This is my "app.component.html" file:
<div class="container">
<!-- Nav Bar (text changes here) -->
<app-nav></app-nav>
<!-- Pages (components which are included in app.router.ts) -->
<router-outlet></router-outlet>
</div>
For e.g. this is "about.component.ts" file:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
const text_for_nav_bar = "This is my new About page."; // <-- text that should be displayed in nav component for this page on router event.
constructor() { }
ngOnInit() {
}
}
Using below code you will able to subscribe router change events. You need to add this code on nav bar.
Import router and Navigation start
import { Router, ActivatedRoute, NavigationStart } from '#angular/router';
import "rxjs/add/operator/filter";
import "rxjs/add/operator/pairwise";
Add below code in constrictor.
this.router.events
.filter(event => event instanceof NavigationStart)
.pairwise()
.subscribe((value: [NavigationStart, NavigationStart]) => {
let nextUrl = value[1].url;
if (nextUrl == '/about') {
// your code here for next url
}
},
(err) => {
},
() => { });
}
});
One way would be with *ngIf (or [hidden] if you want to load all contents to the DOM at once). And to catch current route, inject Router module:
class NavComponent {
constructor(private router: Router){
}
}
and in nav.component.html:
<div *ngIf="router.url === '/some/route'">
text for this route
</div>
<div *ngIf="router.url === '/other/route'">
text for other route
</div>
Doing same in component.ts, could be:
nav.component.html:
<h1>{{yourText}}</h1>
component.ts:
ngOnInit() {
if(this.router.url == '/some/route') {
yourText = 'Text'
} elseif(this.router.url == '/other/route') {
yourText = 'Other text'
}
}
You should use router datas
export const router: Routes = [
{ path: '', redirectTo: 'about', pathMatch: 'full' },
{ path: 'about', component: AboutComponent, data: {navigationText: 'Some text'} },
{ path: 'service', component: ServiceComponent, data: {navigationText: 'Some other text'} }
];
and in app.component.html
<div class="container">
<app-nav text="outlet.activatedRouteData.navigationText"></app-nav>
<router-outlet #outlet="outlet"></router-outlet>
</div>
Of course you need to add a "#Input text: string" property in nav.component.ts
My routerLink work outside of my the router-outlet in the navigation component but when I have a page that is within the router-outlet that has a routerlink to a different page I get an error.
browser_adapter.ts:82 TypeError: Cannot read property 'startsWith' of undefined
at UrlParser.parseRootSegment (url_tree.ts:301)
at DefaultUrlSerializer.parse (url_tree.ts:196)
at Router.navigateByUrl (router.ts:242)
at RouterLinkWithHref.onClick (router_link.ts:90)
at DebugAppView._View_ProfileFeedComponent0._handle_click_12_0 (ProfileFeedComponent.template.js:381)
at eval (view.ts:406)
at eval (dom_renderer.ts:274)
at eval (dom_events.ts:20)
at ZoneDelegate.invoke (zone.js:323)
at Object.onInvoke (ng_zone_impl.ts:72)
My router is a basic router.
app.routes.ts
import { provideRouter, RouterConfig } from '#angular/router';
import { HomeComponent } from "./home/home.component";
import {ProfileFeedComponent} from "./profileFeed/profileFeed.component";
import {QuestionComponent} from "./questions/question.component";
import {QuestionAskComponent} from "./questionAsk/questionAsk.component";
export const routes: RouterConfig = [
{path: '', component: HomeComponent, pathMatch: 'full'},
{path: 'profile-feed', component: ProfileFeedComponent},
{path: 'question', component: QuestionComponent},
{path: 'question/ask', component: QuestionAskComponent},
];
export const appRouterProviders = [
provideRouter(routes)
];
export const appRouterProviders = [
provideRouter(routes)
];
app.component.html
<navigation></navigation>
<div class="wrapper">
<router-outlet></router-outlet>
</div>
app.component.ts
#Component({
moduleId: module.id,
selector: "my-app",
templateUrl: "app.component.html",
directives: [ ROUTER_DIRECTIVES, CORE_DIRECTIVES, FORM_DIRECTIVES, NavigationComponent],
})
export class AppComponent {
public viewContainerRef : ViewContainerRef;
public constructor(viewContainerRef:ViewContainerRef) {
// You need this small hack in order to catch application root view container ref
this.viewContainerRef = viewContainerRef;
}
}
//ALL IMPORTS ARE PROPERLY INCLUDED IN THIS FILE
The problem is in this component I believe I just want to have the option to go to a different page within this page, seems straightforward not sure why it is not working. The below page would be inserted into the router-outlet from the menubar navigation. The link('/questions/ask') within this file is not available in the menu bar.
profileFeed.component.ts
import {Component} from "#angular/core";
import {ROUTER_DIRECTIVES, RouterLink, Router} from "#angular/router";
import {ProfileInfoComponent} from "../profileInfo/profileInfo.component";
#Component({
moduleId: module.id,
selector: 'profile-feed',
templateUrl: 'profileFeed.component.html',
styleUrls: ['profileFeed.component.css'],
directives: [
ProfileInfoComponent,
RouterLink,
ROUTER_DIRECTIVES
]
})
export class ProfileFeedComponent {
}
profileFeed.component.html
<div class="profile-feed-container container">
<div class="profile-detail-summary container-fluid">
<profile-info></profile-info>
<hr>
<div class="container-fluid">
<div class="container-fluid">
<a class="btn btn-default" routerLink='/question/ask'>Ask a Question</a>
</div>
</div>
</div>
</div>
The workaround I found to work is use a click method on the link and set a method inside the component's ts file to go to the route needed by using the Router class' navigateByUrl() method. Example:
Component.ts file
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
}
goToLoginPage() {
this.router.navigateByUrl("");
}
}
Component.html link
<p>Have an account? <a (click)="goToLoginPage()" class="signup">Sign in</a></p>
The page doesn't fully refresh it acts as if it were a regular <router-outlet> link. I also looked at the network tab and didn't see any new items appear.
I'm still working on a project using Angular2.
If you want more details about why I need to do what I'm going to explain, please refer this issue.
I have an AppComponent which is bootstraped via bootstrap. It's a very simple component :
#Component({
selector: 'app-view',
directives: [ Devtools, MainComponent ],
template: `
<ngrx-devtools></ngrx-devtools>
<main-cmp></main-cmp>
`
})
export class AppComponent { }
This component includes another one : MainComponent (via the main-cmp selector). For some reasons, I want to set up my routing in MainComponent.
Here is the code :
#Component({
selector: 'main-cmp',
directives: [ ROUTER_DIRECTIVES, NavComponent ],
template: `
<h1>App</h1>
<nav-cmp></nav-cmp>
<router-outlet></router-outlet>
`
})
#RouteConfig([
{ path: '/home', name: 'Home', component: HomeComponent, useAsDefault: true },
{ path: '/medias', name: 'Medias', component: MediasComponent }
])
export class MainComponent {
constructor (private router:Router, private store:Store<AppStore>) {
router.subscribe(url => store.dispatch(changeUrl(url)));
}
}
Finally, MainComponent includes NavComponent which is a very basic nav.
The thing is, with this setup, I encounter this issue :
EXCEPTION: Component "AppComponent" has no route config. in [['Home'] in NavComponent#2:15].
Of course, if I move my router's logic to AppComponent, everything works well.
So my question is : is there a way to do routing stuff into another component than the one which is bootstraped ?
Thanks :).
It appears that it's not possible because the generate method of the RouteRegistry class explictly relies (hardcoded) on the root component. See this line in the source code:
https://github.com/angular/angular/blob/master/modules/angular2/src/router/route_registry.ts#L345
Here is the code that trhows the error:
RouteRegistry.prototype._generate = function(linkParams,
ancestorInstructions, prevInstruction, _aux, _originalLink) {
(...)
var parentComponentType = this._rootComponent; // <----
(...)
var rules = this._rules.get(parentComponentType);
if (lang_1.isBlank(rules)) { // <----
throw new exceptions_1.BaseException("Component \"" +
lang_1.getTypeNameForDebugging(parentComponentType) +
"\" has no route config.");
}
(...)
};
This method is indirectly used from the _updateLink method of the RouterLink directive.
Here is the corresponding stack trace:
RouteRegistry._generate (router.js:2702)
RouteRegistry.generate (router.js:2669)
Router.generate (router.js:3174)
RouterLink._updateLink (router.js:1205)
See the plunkr I used to debug your problem: https://plnkr.co/edit/8JojtgZmc8kA9ib6zvKS?p=preview.
How about a workaround - use root as a child route?
#Component({
selector: 'app',
directives: [ ROUTER_DIRECTIVES ],
template: `
<router-outlet></router-outlet>
`
})
#RouteConfig([
{path: '/...', as: 'Main', component: MainComponent, useAsDefault: true }
])
export class App { }
#Component({
selector: 'main-cmp',
directives: [ ROUTER_DIRECTIVES ],
template: `
<h1>App</h1>
<router-outlet></router-outlet>
`
})
#RouteConfig([
{ path: '/home', name: 'Home', component: HomeComponent, useAsDefault: true },
])
export class MainComponent { }
My Routing isn't working in Angular2, to demonstrate the point, I have put the same component as the destination for both the root of my site and /login. The component works at http://localhost:3000, but at http://localhost:3000/login, I just get a notice "Cannot GET /login".
app.component.ts:
import { Component } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import {TodoService} from './todo/services/todo.service';
import { TodoCmp } from './todo/components/todo.component';
import { LoginComponent } from './user/components/login.component';
import { UserService } from './user/services/user.service';
#Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<router-outlet></router-outlet>
`,
styleUrls: ['client/dev/todo/styles/todo.css'],
directives: [ROUTER_DIRECTIVES],
providers: [
ROUTER_PROVIDERS,
TodoService
]
})
#RouteConfig([
{
path: '/',
name: 'TodoCmp',
component: TodoCmp,
useAsDefault: true
},
{
path: '/login',
name: 'TodoCmp',
component: TodoCmp
}
])
export class AppComponent {
title = 'ng2do';
}
Here is a link to my index file.
What have I done wrong?
Two routes in one #RouteConfig(...) can't have the same name:
#RouteConfig([
{
path: '/',
name: 'TodoCmp',
component: TodoCmp,
useAsDefault: true
},
{
path: '/login',
name: 'TodoCmp', <!-- <<<== should be 'Login' instead of 'TodoCmp'
component: TodoCmp
}
])
You should move ROUTER_PROVIDERS to bootstrap() (like HTTP_PROVIDERS)
Angular 2 - Is it possible to store my routes in another file and import them into the app.ts file (because over time the routes will build up)
Here is an example of my current app.ts that works. I basically want to move the route config routes to another file to make it cleaner:
import {Todo} from './components/todo/todo';
import {About} from './components/about/about';
import {AuthService} from './authService';
import {Component, View, bootstrap, bind, provide} from 'angular2/angular2';
import {Router, ROUTER_BINDINGS, RouterOutlet, RouteConfig, RouterLink, ROUTER_PROVIDERS, APP_BASE_HREF} from 'angular2/router';
import {Location, LocationStrategy, HashLocationStrategy} from 'angular2/router';
#Component({
selector: 'app'
})
#View({
template: `
<div class="container">
<nav>
<ul>
<li><a [router-link]="['/Home']">Todo</a></li>
<li><a [router-link]="['/About']">About</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
</div>
`,
directives: [RouterOutlet, RouterLink]
})
#RouteConfig([
{ path: '/', redirectTo: '/home' },
{ path: '/home', component: Todo, as: 'Home' },
{ path: '/about', component: About, as: 'About' }
])
export class AppComponent {
constructor(router: Router, _authService: AuthService, _location: Location){
//Subscribe - watches routes pop state events.
router.subscribe((val) => {
_authService.isUserLoggedIn().then((success) => {
router.parent.navigate(['/About']);
});
})
}
}
bootstrap(AppComponent, [ROUTER_PROVIDERS, provide(APP_BASE_HREF, {useValue: '/'}), AuthService]);
i personally have created an route.interface.ts and a route.ts files.
Routes file
import {Route} from './route.interface'
import {AuthComponent} from './auth/auth.component'
export const Routes: Route[] = [
{
path: '/auth',
name: 'Authenticate',
component: AuthComponent
},
];
Route Interface
export interface Route {
path: string,
name: string,
component: any,
}
Usage in main component.
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router'
import {Routes} from './routes'
#Component({
selector: 'app',
templateUrl: './angular2/app/layout.component.html',
directives: [
ROUTER_DIRECTIVES
],
providers: [
HTTP_PROVIDERS,
ROUTER_PROVIDERS
],
})
#RouteConfig(Routes)
Hope that helps. you can even create a route service and inject it you main component. Enjoy coding!
You can add your RouteConfig per component
Lets say you have home and about as in your example, then you would define the routing from that specific component in the component itself.
So in your about component you can add
// './components/about/about'
#RouteConfig([
{ path: '/about', component: About, as: 'About' }
])
And in your home component you can do the same
// './components/home/home'
#RouteConfig([
{ path: '/home', component: Todo, as: 'Home' }
])