In my Angular app I have a component where I'm loading a grid view of some data being returned from the API. I also have some filters on the component, that the user can make use of to filter the data being returned.
This all works as expected -- until the user navigates away, and then returns to the component. At that point the filter selections are no longer being applied. In other words, the component is re-loaded in its "virgin" state, as if no filters had been selected.
So what I think I need to do is use ngOnInit() to read the url state from the browser window -- since all of the filters are still there at this point, as part of the query string, like so:
http://localhost:4200/consulting;page=1;pn_firstName.e=1;pn_firstName.v=John;pn_lastName.e=;pn_lastName.v=
According the above url/query string, the data should load after filtering on "firstName":"John".
So, my question is, what does the syntax look like in Angular to pre-load from the url like this within ngOnInit? Something like this?
ngOnInit() {
this.router.navigateByUrl(`consulting/${}`); // Should be current url/query string in the browser window
}
Basically I just need to know what the syntax should look like when I want this to be read dynamically from the current state of the url/query string in the browser window. Because, that way, whatever filters had been applied will work accordingly upon re-load of the component -- because they're still available in the query string.
UPDATE:
After a comment below, I'm thinking I could do something like this:
constructor(private route: ActivatedRoute) {}
And then, because this is an observable, I'd do something like:
ngOnInit() {
this.loadData()
}
loadData() {
this.router.navigate([this.route.queryParams.toArray()]);
}
Will something like this work?
Inject activatedRoute: ActivatedRoute into your component and the query params are available either through activatedRoute.queryParams or activatedRoute.queryParamMap.
See the docs about router.
Here is simple stackblitz demo which just renders the query params and you can play with the query parameters here.
Related
I am trying to understand what a basic implementation of loading a routerLink while also pulling in saved url params would look like. Normally, the way I handle routing in my app is via a subscribing to an observable, that looks like this:
private onFilterProcessed(value: any, type: string, body, page)
{
if (type === 'zip')
{
this.processType('addresses.zip', value);
} if (type === 'location')
{
this.processType('addresses.city', value);
this.filtersService.getByCategory(
page, this.pagesize, this.body)
.subscribe(resRecordsData => {
let data = resRecordsData.data;
},
responseRecordsError => this.errorMsg = responseRecordsError);
}
This allows me to pass in some filter parameters to the api call as part of the body in a POST request. This will return results where the user's filter selections are passed in before returning the data.
This all works as expected. When a user goes "back" to a previously loaded component, their previous filter selections will be passed into the api call, so the "page" looks just like it did when they were last on that page/component.
However, I also have a couple of sections in my app where I'm loading components via routerLink instead. They initially looked like this:
<a routerLink="/customer-history" routerLinkActive="selected">Customer History</a>
The thing is, now that I have filter params in the url, this alone won't work, because each time I click on these particular links, it will wipe out the url and re-load it with just the page identifier "customer-history" -- because that's what I'm currently telling it to do.
If, for instance, the filters had been used by the user to filter results based on a city, the url would look like this:
http://localhost:4200/customer-history;locations=true;locations_place=Seattle
So the problem is, if they were to click away, and then RETURN to that page/component via the routerLink link, instead of getting the filtered data for that page, it will instead load this:
http://localhost:4200/customer-history
So my question is about how to pass those url params in as part of the routerLink. I assume it would look something like this, with square brackets for binding:
<a [routerLink]="['/customer-history', getParams()]" routerLinkActive="selected">Customer History</a>
What I'm not clear on is how I get those specific url params (just the filter params, not the component/page name) and pass them in via binding like this.
I know Angular makes available activatedRoute.snapshot, which I could get like this, to pass into getParams():
getParams()
{
return this.activatedRoute.snapshot;
}
But this will return the full url, not just the filter params part, which is what I need. So how would I get the part of the url I need, and pass it in here to append to "customer-history" in the url?
What would that look like in a basic implementation?
A way to resolve this, instead of using routerLink in the template, is to pass a function that resolves the correct page/component while subscribing to and navigating to that page and all relevant url params.
To do this I do this in the view:
<a (click)="goToCustomerHistory()">Customer History</a>
And that function in the component looks like this:
goToCustomerHistory()
{
this.route.params.subscribe(
(params: any) => {
this.page = params['page'];
this.locations = params['locations'];
this.locations_place = params['locations_place'];
}
);
this.router.navigate(
['/customer-history', {
page: this.page,
locations = this.locations;
locations_place = this.locations_place;
}]);
}
And of course, you also need to import Router and ActivatedRoute and inject in the constructor:
constructor(private router: Router,
private route: ActivatedRoute){}
I am building an angular 2 application. The documentation has changed quite a bit since the released which has caused confusion. The best I can do is explain what I am trying to do (Which was easy in Angular 1) and hope someone can help me out.
I have created a login service using JWT's.
Once login is successful, I return a user object.
I have a loginComponent ( binds data to template ) and loginService ( which handles the https calls )
I have a userService which maintains the user object.
I have a userComponent which renders the user data.
The problem is, once the user has logged in, I am unclear on the best approach for letting the userService retrieve the new data in an object called "user", then the userComponent update its user object on the template. This was easy in angular 1 simply by putting a watcher on the userService.user object.
I tried Inputs and Outputs to no avail, eventEmitters, Observables and getters and setters. The getters and setters work, but force me to store everything in a "val()"
Can someone please tell me the best way to achieve this?
User Component renders template with user.firstName, user.lastName etc.
Initially user if an empty Object
The login service needs to set the UserService.user
The userComponent Needs to detect the change and update the DOM.
Thanks in ADVANCE!
If I'm not wrong, you are looking for a way to 'listen' to changes in your UserService.user to make appropriate updates in your UserComponent. It is fairly easy to do that with Subject (or BehaviorSubject).
-In your UserService, declare a property user with type Subject<User>.
user: Subject<User> = new Subject();
-Expose it to outside as observable:
user$: Observable<User>
...
this.user$ = this.user.asObservable();
-Login function will update the private user Subject.
login(userName: string, password: string) {
//...
this.user.next(new User("First name", "Last name"));
}
-In your UserComponent, subscribe to UserServive's user$ observable to update view.
this.userService.user$.subscribe((userData) => {this.user = userData;});
-In your view, simply use string interpolation:
{{user?.firstName}} {{user?.lastName}}
Here is the working plunker: http://plnkr.co/edit/qUR0spZL9hgZkBe8PHw4?p=preview
There are two rather different approaches you could take:
1. Share data via JavaScript reference types
If you create an object in your UserService
#Injectable()
export class UserService {
public user = new User();
you can then share that object just by virtue of it being a JavaScript reference type. Any other service or component that injects the UserService will have access to that user object. As long as you only modify the original object (i.e., you don't assign a new object) in your service,
updateUser(user:User) {
this.user.firstName = user.firstName;
this.user.lastName = user.lastName;
}
all of your views will automatically update and show the new data after it is changed (because of the way Angular change detection works). There is no need for any Angular 1-like watchers.
Here's an example plunker.
In the plunker, instead of a shared user object, it has a shared data object. There is a change data button that you can click that will call a changeData() method on the service. You can see that the AppComponent's view automatically updates when the service changes its data property. You don't have to write any code to make this work -- no getter, setter, Input, Output/EventEmitter, or Observable is required.
The view update automatically happens because (by default) Angular change detection checks all of the template bindings (like {{data.prop1}}) each time a monkey-patched asynchronous event fires (such as a button click).
2. "Push" data using RxJS
#HarryNinh covered this pretty well in his answer. See also Cookbook topic Parent and children communicate via a service. It shows how to use a Subject to facilitate communications "within a family".
I would suggest using a BehaviorSubject instead of a Subject because a BehaviorSubject has the notion of "the current value", which is likely applicable here. Consider, if you use routing and (based on some user action) you move to a new route and create a new component, you might want that new component to be able check the "current value" of the user. You'll need a BehaviorSubject to make that work. If you use a regular Subject, the new component will have no way to retrieve the current value, since subscribers to a Subject can only get newly emitted values.
So, should we use approach 1. or 2.? As usual, "it depends". Approach 1. is a lot less code, and you don't need to understand RxJS (but you do need to understand JavaScript reference types). Approach 2. is all the rage these days.
Approach 2. could also be more efficient than 1., but because Angular's default change detection strategy is to "check all components", you would need to use the OnPush change detection strategy and markForCheck() (I'm not going to get into how to use those here) to make it more efficient than approach 1.
Abstract
Hi, I'm using angular + ui-router in my project, I have huge amount of nested states and different views that in turn contain huge amount of different inputs, a user fills these inputs incrementally step by step.
The problem
Sometimes users require additional info that is located on the previous step, and browsers "back" button helps the user to sneak peek into that data, but as soon as the user presses it, the info he already entered is lost due to state transition, which is obviously a bad thing.
Strategy
In order to overcome described problem I have the following plan:
Associate each user's "navigation" (guess this is a proper term) with a random id
To prevent scope-inheritance-and-serialization issues, instead of putting viewmodel into $scope use ordinary javascript object that will be storing immediate values that are bound to UI.
Add watcher to look for changes on that "storage object"
As soon as the change spotted, serialize the object and persist it
Explanations
Why do we need a random parameter in URL?
We don't want to store all data in URL, since there might be quite some amount of data that wont fit into URL. So in order to provide the guarantees the URL won't break, we put only small random GUID/UUID into it that later allows obtaining the data associated with current "navigation" by this random GUID/UUID.
The storage
There are multitude of storage scenarios available out there: LocalStorage, IndexedDB, WebSQL, Session Storage, you name it, but due to their cross-tab, cross-browser, browser-specific nature it would be hard to manipulate and manage all of the data that gets into the storage. The implementation will be buggy / might require server-side support.
So the most elegant storage strategy for this scenario would be storing data in special window.name variable which is capable of storing data in-between requests. So the data is safe until you close your tab.
The Question
On behalf of everything written above, I have the root view called "view" that has a state parameter id (this is the random GUID/UUID)
$stateProvider.state('view', {
url: '/view/{id}',
controller: 'view',
templateUrl: 'views/view.html'
});
All of the other views derive from this view, is there way to make ui-sref directive to automatically inject a random GUID/UUID into id state parameter of my root view, instead of writing each time ui-sref's like:
<a ui-sref="view({id:guid()}).someNestedView({someNestedParam: getParam()})"
I would like to have something like:
<a ui-sref="view.someNestedView({someNestedParam: getParam()})"
The AOP and Decorator pattern are the answer. The comprehensive description could be found here:
Experiment: Decorating Directives by Jesus Rodriguez
Similar solution as described below, could be observed:
Changing the default behavior of $state.go() in ui.router to reload by default
How that would work? There is a link to working example
In this case, we do not solve from which source the random GUID comes from. Let's just have it in runtime:
var guidFromSomeSource = '70F81249-2487-47B8-9ADF-603F796FF999';
Now, we can inject an Decorator like this:
angular
.module('MyApp')
.config(function ($provide) {
$provide.decorator('$state', function ($delegate) {
// let's locally use 'state' name
var state = $delegate;
// let's extend this object with new function
// 'baseGo', which in fact, will keep the reference
// to the original 'go' function
state.baseGo = state.go;
// here comes our new 'go' decoration
var go = function (to, params, options) {
params = params || {};
// only in case of missing 'id'
// append our random/constant 'GUID'
if (angular.isUndefined(params.id)) {
params.id = guidFromSomeSource;
}
// return processing to the 'baseGo' - original
this.baseGo(to, params, options);
};
// assign new 'go', right now decorating the old 'go'
state.go = go;
return $delegate;
});
})
Code should be self explanatory, check it in action here
I have an app that is currently built to have a static base URL with a parameter at the end. I would like to instead have the base URL default to one vaule, but have the ability to built routes based on several options. So for now its set up as:
.state('ball', {
parent: 'ballLayout',
url: '/ball/{urlName}',
views: {
'cube.head': {
templateUrl: 'partials/views/ball.head.html',
controller: 'BallCtrl'
}
}
});
The static ball value is what I'd like to change. Basically I'd like to have an optional list of incoming URLs that would work, but when nothing is present it defaults to ball. So for instance:
ball/title-of-page
bat/title-of-page
basket/title-of-page
beast/title-of-page
These would all work, and when constructing the URL it would default to ball/
Is something like this possible? How would one go about implementation.
I dont think what I'm asking here can actually be done without having issues with other parameters. Instead Im asking a new question about Regex from incoming links to reroute to my angular URL.
I have some route like /ads/:ad_id and from my controller I can do
this.transitionToRoute('ads.ad', adObj)
How can I do the similar thing but this time passing the ID instead of the loaded object?
O course I understand that I can load an obj by ID first, but Ember's power is in doing lost of boilerplate for us.
Update: So, as by default Ember serializes the model to URL params by doing like
mode_instance -> { model_name_id: model_instance.id }
My trivial attempt was doing
this.transitionToRoute('ads.ad', { id: adObjId })
But when passed a model object Ember does not re-fetch it.
So, the question: I have a route (single ad view) that depends on ad ID. I have this ID as number. I want to transition to this route like if I simply entered the url /ads/ID
This can be accomplish by passing the URL to transitionTo. For example,
this.transitionToRoute('/ads/' + adObjId)
The model() method will be called with the params from the URL.
Here is a use case for this:
Transitioning from a list view to a detail view. In the list view, the records don't have any relations tied to them, but the detailed view should side-load relational data. For this reason, the models are not 1:1 between the list view and detailed view. There should be a way to transition simply using the id.
Cp
What's your use case for this? Most cases when you would want to specify an object by id, you already have the object to pass to transitionTo. Can you provide more context about what you're trying to do? I think you can probably accomplish it without using the object id.
In any case, I don't think there's a good way to do this, because when you transition via transitionTo(someRoute, someModel), the route's model hook is not called, and the model you pass in (someModel) is supplied directly to the other route hooks (setupController(controller, model), redirect(model), renderTemplate(controller, model)).
See Ember.JS Route api -- model method for more details.