I Need to implement 'search' by passing queryParams through route from the search component to the userList component (example. /search-result?user="Alfred"). Before loading the userList component, i need to make an API call using the queryParams in the userList resolver but the query params keeps showing undefined.
Search Component
search(searchTerm: string) {
if (searchTerm) {
this.router.navigate(['search-result'], { queryParams: { user: searchTerm } });
}
}
UserList Resolver
export class UserResolver implements Resolve<User[]> {
constructor(private userService: UserService, private route: ActivatedRoute) { }
resolve(): Observable<User[]> {
const searchTerm: string = this.route.snapshot.queryParams['user'];
console.log(searchTerm); //Logs Undefined
return this.userService.getUsers(searchTerm);
}
}
On latest versions of Angular you can get the ActivatedRouteSnapshot on the resolver function.
export class UserResolver implements Resolve<User[]> {
constructor(private userService: UserService, private route: ActivatedRoute) { }
resolve(**route: ActivatedRouteSnapshot**): Observable<User[]> {
**console.log(route.queryParams)**
return this.userService.getUsers(searchTerm);
}
}
Maybe the resolve function is running before the queryParams are populated in the url. Try doing it in an Rxjs way.
import { filter, map, switchMap, tap } from 'rxjs/operators';
...
export class UserResolver implements Resolve<User[]> {
constructor(private userService: UserService, private route: ActivatedRoute) { }
resolve(): Observable<User[]> {
return this.route.queryParams.pipe(
tap(params => console.log(`Params: ${params}`)),
// wait until params has user in it
filter(params => !!params['user']),
tap(params => console.log('after filter')),
// extract the value of the user param
map(params => params['user']),
// switch to a new observable stream once we know the searchTerm
switchMap(searchTerm => this.userService.getUsers(searchTerm)),
);
}
}
Edit
Use the tap operator to debug the stream. See what the log is and make sure console.log(Params: ${params}) has the user params.
Edit2
Try
this.router.navigateByUrl(`/search-result?user=${searchTerm}`);
, I am thinking there is something wrong with how you navigate.
Edit 3
I am thinking queryParams can only be read when the component itself loads and not at the run time of the route resolvers because it is saying, I need to go to the route of search-result, give me the data before I go to search-result and it is independent of the queryParams. To fix this, I followed this guide (https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html).
1.) In app-routing-module.ts, change the registration of the path to:
{ path: 'search-result/:user', component: UserListComponent, resolve: { users: UserResolver } },
Now the user will be the parameter we are after in the URL.
2.) In search.component.ts, change search to:
search(searchTerm: string) {
if (searchTerm) {
this.router.navigate([`search-result/${searchTerm}`]);
}
}
3.) In user-resolver.service.ts, change it to this:
#Injectable({
providedIn: 'root'
})
export class UserResolver implements Resolve<User[]> {
constructor(private userService: UserService) { }
resolve(route: ActivatedRouteSnapshot): Observable<User[]> {
const searchTerm: string = route.paramMap.get('user');
return this.userService.getUsers(searchTerm);
}
}
I when console logging searchTerm, it is the accurate value. Thanks for providing the StackBlitz, it helped you and me.
Passing the URL id from the last page a user was on to a service that I can reference in a dialog.
issuer.service.ts
import { Injectable, EventEmitter } from '#angular/core';
import { Observable, of } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class IssuerService {
private urlidSource = new BehaviorSubject<string>('');
currentUrlid = this.urlidSource.asObservable();
public onChange: EventEmitter<string> = new EventEmitter<string>();
constructor () {
}
changeUrlid(urlid: string) {
this.currentUrlid = of(urlid);
this.onChange.emit(urlid);
}
getUrlid(currentUrlid: string) {
return this.currentUrlid;
}
}
Page that has the URL id I want (dashboard.component.ts)
import { IssuerService } from './../../issuer.service';
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
urlid: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
public dialog: MatDialog
) {}
newUrlid() {
this.issuerService.changeUrlid(this.route.snapshot.paramMap.get('id'));
console.log(this.urlid);
}
ngOnInit() {
// Get URL ID
this.issuerService.onChange.subscribe(urlid => this.urlid = urlid);
this.newUrlid();
}
Component I want to read the value in:
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
import { IssuerService } from './../../issuer.service';
urlid: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
public dialog: MatDialog
) {}
ngOnInit() {
this.issuerService.onChange.subscribe(urlid => {
this.urlid = urlid;
console.log(this.urlid);
});
}
So currently when I visit my dashboard page it will display the value of 2 which is correct. My goal is that when a user visits any page I can read this value of 2. How can I access this value? The above code works and my Header displays 2 but only when on the dashboard page. I need it to display 2 no matter what page the user is on.
you can see this example, and It's modified list:
use queryPamas to get query string, not params (DashboardComponent)
use ReplaySubject(1) to return the last urlId; it's don't have a default value, just return prev one value (IssuerService)
get observable from getUrlid and subscribe it in components that want to show url id
export class IssuerService {
private urlidSource = new ReplaySubject<string>(1);
constructor() {
}
changeUrlid(urlid: string) {
this.urlidSource.next(urlid);
}
getUrlid() {
return this.urlidSource;
}
}
export class DashboardComponent implements OnInit {
urlid: string;
constructor(
// private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
// public dialog: MatDialog
) { }
newUrlid() {
// Get URL ID
this.route.queryParams.subscribe((queryParam) => {
const id = queryParam['id'];
if (!id) {
return;
}
this.issuerService.changeUrlid(id);
});
}
ngOnInit() {
this.newUrlid();
this.issuerService.getUrlid().subscribe(urlid => {
this.urlid = urlid;
});
}
}
export class HelloComponent implements OnInit {
urlid;
constructor(
private issuerService: IssuerService
) { }
ngOnInit() {
this.issuerService.getUrlid().subscribe(urlid => {
this.urlid = urlid;
});
}
}
You do not need a parameter for your get Method since you already have the value inside the service,
getUrlid() {
return this.currentUrlid;
}
and you can use retrieve the value in the 2nd component as follows,
this.issuerService.currentUrlid.subscribe((value: string) => {
this.urlid = value;
}
I have an Angular setup that uses two guard. canLoad and canActivate
both get fed with the same observable from the #angular-redux/store via #select
Question: Why does canActivate work with the observable that #select returns while canLoad breaks all routing from then on? What is the difference between the two guards?
Related angular issue: https://github.com/angular/angular/issues/18991
auth.guard.ts
#Injectable()
export class AuthGuard implements CanLoad, CanActivate {
#select() readonly authenticated$: Observable<boolean>; // #angular-redux/store
canLoad(): Observable<boolean> | boolean {
// return true; // works
return this.authenticated$; // ERROR: all routing stops from and to the current page
}
canActivate(): Observable<boolean> | boolean {
// return true; // works
return this.authenticated$; // works
}
}
app-routing.module
const routes: Routes = [
{
path: '',
component: SomeAComponent,
pathMatch: 'full'
},
{
path: 'someb',
component: SomeBComponent,
canActivate: [
AuthGuard
],
},
{
path: 'lazy',
loadChildren: './lazy/lazy.module#LazyModule',
canLoad: [
AuthGuard
]
},
{
path: '**',
redirectTo: '/'
}
];
The same issue I had, so to resolve it and let working in both CanLoad and CanActivate, you should add operator take(1)
#Injectable()
export class AuthGuard implements CanLoad, CanActivate {
#select() readonly authenticated$: Observable<boolean>; // #angular-redux/store
canLoad(): Observable<boolean> | boolean {
// return true; // works
return this.authenticated$.pipe(take(1));
}
canActivate(): Observable<boolean> | boolean {
// return true; // works
return this.authenticated$;
}
}
I just ran into the same issue and I do think that this is a bug in angular. I ended up just rewriting my guard to store a local variable that is populated by subscribing to the Observable. I am using ngrx/store here.
#Injectable()
export class MustBeAuthenticatedGuard implements CanActivate, CanLoad {
constructor(private store: Store<fromAuth.State>) {
store.select(fromAuth.authenticated)
.subscribe((authenticated) => {
this.authenticated = authenticated;
});
}
private authenticated: boolean
canLoad(): boolean {
return this.isAuthenticated();
}
canActivate(): boolean {
return this.isAuthenticated();
}
private isAuthenticated() {
if (!this.authenticated) {
this.store.dispatch(new SignIn());
}
return this.authenticated;
}
}
I plan to do such architecture:
component store
-- nested-component book
in store - i have an service call, which get data from service, and i do a subscription on result. Like it was described in angular2 docs (http).
And i want to use this data in nested components: in forms (formBuilder), in material-design elements etc.
Which way is the best, to do this? I'm new to angular2.
Store:
book: IBook;
constructor(private bookService: BookService) { }
ngOnInit() {
this.bookService.getBook('1')
.subscribe((book) => {
this.book = book;
});
}
BookService:
...
getBook (id): Observable<IBook> {
return this.http.get(this.url + '/' + id)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
...
Book:
#Input() book:IBook;
constructor() {}
ngOnInit() {
/*How here can i subscribe on book http data get?, so that i can use async value in forms etc?*/
});
Because, if i use async book everywhere (not formBuilder) - all is ok, but formBuilder is in need to update values, after data is loaded in parent component. How can i do this?
What about passing the bookID to the BookComponent and letting the BookComponent handle the async http get in ngInit?
export class Book implements OnInit {
#Input() bookID: number;
private book: IBook;
constructor(private bookService: BookService) {}
ngOnInit() {
this.bookService.getBook(this.bookID)
.subscribe((book) => {
this.book = book;
});
}
}
Otherwise you have a few options which are explained in https://angular.io/docs/ts/latest/cookbook/component-communication.html
I'll briefly highlight two ways which I think you could use.
Intercept input property changes with ngOnChanges
export class Book implements OnChanges {
#Input() book: IBook;
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
for (let propName in changes) {
// handle updates to book
}
}
}
more info https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Parent and children communicate via a service
#Injectable()
export class BookService {
books = new Subject<IBook>();
getBook(id): Observable<IBook> {
return this.http.get(this.url + '/' + id)
.map(d => {
let book = this.extractData(d);
this.books.next(book);
return book;
})
.catch(this.handleError);
}
...
}
#Component({
selector: 'book',
providers: []
})
export class Book implements OnDestroy {
book: IBook
subscription: Subscription;
constructor(private bookService: BookService) {
this.subscription = bookService.books.subscribe(
book => {
this.book = book;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
#Component({
selector: 'store',
providers: [BookService]
})
export class Store {
book: IBook;
constructor(private bookService: BookService) { }
ngOnInit() {
this.bookService.getBook('1')
.subscribe((book) => {
this.book = book;
});
}
}
How do I get the RouteParams from a parent component?
App.ts:
#Component({
...
})
#RouteConfig([
{path: '/', component: HomeComponent, as: 'Home'},
{path: '/:username/...', component: ParentComponent, as: 'Parent'}
])
export class HomeComponent {
...
}
And then, in the ParentComponent, I can easily get my username param and set the child routes.
Parent.ts:
#Component({
...
})
#RouteConfig([
{ path: '/child-1', component: ChildOneComponent, as: 'ChildOne' },
{ path: '/child-2', component: ChildTwoComponent, as: 'ChildTwo' }
])
export class ParentComponent {
public username: string;
constructor(
public params: RouteParams
) {
this.username = params.get('username');
}
...
}
But then, how can I get this same 'username' parameter in those child components? Doing the same trick as above, doesn't do it. Because those params are defined at the ProfileComponent or something??
#Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(
public params: RouteParams
) {
this.username = params.get('username');
// returns null
}
...
}
UPDATE:
Now that Angular2 final was officially released, the correct way to do this is the following:
export class ChildComponent {
private sub: any;
private parentRouteId: number;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.sub = this.route.parent.params.subscribe(params => {
this.parentRouteId = +params["id"];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
ORIGINAL:
Here is how i did it using the "#angular/router": "3.0.0-alpha.6" package:
export class ChildComponent {
private sub: any;
private parentRouteId: number;
constructor(
private router: Router,
private route: ActivatedRoute) {
}
ngOnInit() {
this.sub = this.router.routerState.parent(this.route).params.subscribe(params => {
this.parentRouteId = +params["id"];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
In this example the route has the following format: /parent/:id/child/:childid
export const routes: RouterConfig = [
{
path: '/parent/:id',
component: ParentComponent,
children: [
{ path: '/child/:childid', component: ChildComponent }]
}
];
You shouldn't try to use RouteParams in your ChildOneComponent.
Use RouteRegistry, instead!
#Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(registry: RouteRegistry, location: Location) {
route_registry.recognize(location.path(), []).then((instruction) => {
console.log(instruction.component.params['username']);
})
}
...
}
UPDATE: As from this pull request (angular beta.9): https://github.com/angular/angular/pull/7163
You can now access to the current instruction without recognize(location.path(), []).
Example:
#Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(_router: Router) {
let instruction = _router.currentInstruction();
this.username = instruction.component.params['username'];
}
...
}
I haven't tried it, yet
Further details here:
https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta9-2016-03-09
https://angular.io/docs/ts/latest/api/router/Router-class.html
UPDATE 2:
A small change as from angular 2.0.0.beta15:
Now currentInstruction is not a function anymore. Moreover, you have to load the root router. (thanks to #Lxrd-AJ for reporting)
#Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(_router: Router) {
let instruction = _router.root.currentInstruction;
this.username = instruction.component.params['username'];
}
...
}
As mentioned by Günter Zöchbauer, I used the comment at https://github.com/angular/angular/issues/6204#issuecomment-173273143 to address my problem. I used the Injector class from angular2/core to fetch the routeparams of the parent. Turns out angular 2 does not handle deeply nested routes. Maybe they'll add that in the future.
constructor(private _issueService: IssueService,
private _injector: Injector) {}
getIssues() {
let id = this._injector.parent.parent.get(RouteParams).get('id');
this._issueService.getIssues(id).then(issues => this.issues = issues);
}
I found an ugly but working solution, by requesting the parent (precisely the 2nd ancestor) injector, and by getting the RouteParams from here.
Something like
#Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(injector: Injector) {
let params = injector.parent.parent.get(RouteParams);
this.username = params.get('username');
}
}
RC5 + #angular/router": "3.0.0-rc.1 SOLUTION: It seems that this.router.routerState.queryParams has been deprecated. You can get the parent route params this way:
constructor(private activatedRoute: ActivatedRoute) {
}
this.activatedRoute.parent.params.subscribe(
(param: any) => {
let userId = param['userId'];
console.log(userId);
});
You can take component of parent route inside of child component from injector and then get any from child component. In you case like this
#Component({
...
})
export class ChildOneComponent {
public username: string;
constructor(
public params: RouteParams
private _injector: Injector
) {
var parentComponent = this._injector.get(ParentComponent)
this.username = parentComponent.username;
//or
this.username = parentComponent.params.get('username');
}
...
}
Passing Injector instance to constructor in child component may not be good if you want to write unit tests for your code.
The easiest way to work around this is to have a service class in the parent component, in which you save your required params.
#Component({
template: `<div><router-outlet></router-outlet></div>`,
directives: [RouterOutlet],
providers: [SomeServiceClass]
})
#RouteConfig([
{path: "/", name: "IssueList", component: IssueListComponent, useAsDefault: true}
])
class IssueMountComponent {
constructor(routeParams: RouteParams, someService: SomeServiceClass) {
someService.id = routeParams.get('id');
}
}
Then you just inject the same service to child components and access the params.
#Component({
template: `some template here`
})
class IssueListComponent implements OnInit {
issues: Issue[];
constructor(private someService: SomeServiceClass) {}
getIssues() {
let id = this.someService.id;
// do your magic here
}
ngOnInit() {
this.getIssues();
}
}
Note that you should scope such service to your parent component and its child components using "providers" in parent component decorator.
I recommend this article about DI and scopes in Angular 2: http://blog.thoughtram.io/angular/2015/08/20/host-and-visibility-in-angular-2-dependency-injection.html
In RC6, router 3.0.0-rc.2 (probably works in RC5 as well), you can take route params from the URL as a snapshot in case that params won't change, without observables with this one liner:
this.route.snapshot.parent.params['username'];
Don't forget to inject ActivatedRoute as follows:
constructor(private route: ActivatedRoute) {};
With RxJS's Observable.combineLatest, we can get something close to the idiomatic params handling:
import 'rxjs/add/operator/combineLatest';
import {Component} from '#angular/core';
import {ActivatedRoute, Params} from '#angular/router';
import {Observable} from 'rxjs/Observable';
#Component({ /* ... */ })
export class SomeChildComponent {
email: string;
id: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
Observable.combineLatest(this.route.params, this.route.parent.params)
.forEach((params: Params[]) => {
this.id = params[0]['id'];
this.email = params[1]['email'];
});
}
}
I ended up writing this kind of hack for Angular 2 rc.1
import { Router } from '#angular/router-deprecated';
import * as _ from 'lodash';
interface ParameterObject {
[key: string]: any[];
};
/**
* Traverse route.parent links until root router and check each level
* currentInstruction and group parameters to single object.
*
* e.g.
* {
* id: [314, 593],
* otherParam: [9]
* }
*/
export default function mergeRouteParams(router: Router): ParameterObject {
let mergedParameters: ParameterObject = {};
while (router) {
let currentInstruction = router.currentInstruction;
if (currentInstruction) {
let currentParams = currentInstruction.component.params;
_.each(currentParams, (value, key) => {
let valuesForKey = mergedParameters[key] || [];
valuesForKey.unshift(value);
mergedParameters[key] = valuesForKey;
});
}
router = router.parent;
}
return mergedParameters;
}
Now in view I collect parameters in view instead of reading RouteParams I just get them through router:
#Component({
...
})
export class ChildishComponent {
constructor(router: Router) {
let allParams = mergeRouteParams(router);
let parentRouteId = allParams['id'][0];
let childRouteId = allParams['id'][1];
let otherRandomParam = allParams.otherRandomParam[0];
}
...
}
In FINAL with little help of RXJS you can combine both maps (from child and parent):
(route) => Observable
.zip(route.params, route.parent.params)
.map(data => Object.assign({}, data[0], data[1]))
Other questions one might have:
Is it really a good idea to use above - because of coupling (couple child component with parent's param's - not on api level - hidden coupling),
Is it proper approach in term of RXJS (it would require hardcore RXJS user feedback ;)
You can do it on the snapshot with the following, but if it changes, your id property will not be updated.
This example also shows how you can subscribe to all ancestor parameter changes and look for the one you are interested in by merging all of the parameter observables. However, be careful with this method because there could be multiple ancestors that have the same parameter key/name.
import { Component } from '#angular/core';
import { ActivatedRoute, Params, ActivatedRouteSnapshot } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/observable/merge';
// This traverses the route, following ancestors, looking for the parameter.
function getParam(route: ActivatedRouteSnapshot, key: string): any {
if (route != null) {
let param = route.params[key];
if (param === undefined) {
return getParam(route.parent, key);
} else {
return param;
}
} else {
return undefined;
}
}
#Component({ /* ... */ })
export class SomeChildComponent {
id: string;
private _parameterSubscription: Subscription;
constructor(private route: ActivatedRoute) {
}
ngOnInit() {
// There is no need to do this if you subscribe to parameter changes like below.
this.id = getParam(this.route.snapshot, 'id');
let paramObservables: Observable<Params>[] =
this.route.pathFromRoot.map(route => route.params);
this._parametersSubscription =
Observable.merge(...paramObservables).subscribe((params: Params) => {
if ('id' in params) {
// If there are ancestor routes that have used
// the same parameter name, they will conflict!
this.id = params['id'];
}
});
}
ngOnDestroy() {
this._parameterSubscription.unsubscribe();
}
}
Getting RouteParams from parent component in Angular 8 -
I have a route http://localhost:4200/partner/student-profile/1234/info
Parent route - student-profile
Param - 1234 (student_id)
Child route - info
Accessing param in child route (info) -
Import
import { ActivatedRoute, Router, ParamMap } from '#angular/router';
Constructor
constructor(private activatedRoute: ActivatedRoute, private router: Router) { }
Accessing parent route params
this.activatedRoute.parent.paramMap.subscribe((params: ParamMap) => this.studentId = (params.get('student_id')));
Now, our variable studentId has the param value.