share data between two angular component - javascript

I am angular 2.0 beginner and wondered if anyone can help me with passing data from one component to another.
My project is made up of three parts, a login window component, a table component and center component to handle routing. when the users log in in the login component, a http.post request will be sent to server to authenticate. if the credentials are authentic, a json containing users' information will be returned from the server . The page will also be routed to a table component showing the name of this user and his/her other info.
these two component are on the same page using router-outlet.
Here is my app.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'td-app',
template: `
<router-outlet></router-outlet>
`
})
export class AppComponent {
}
app.module.ts:
#NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
JsonpModule,
RouterModule.forRoot([
{
path: 'login',
component: LoginComponent
},{
path: 'table',
component: TableComponent
},{
path: '',
redirectTo: '/login',
pathMatch: 'full'
}
])
],
Here is the data I wanna pass into another component. the postUser method is doing http.post and an json string will return. I wanna pass this json data to my next component. I was thinking using promise to make sure the data is assigned to the variable userInfoPack and then pass userInfoPack to next component's template.
login.component.ts:
postUser(body: User): Promise<any>{
return Promise.resolve( this.getUser(body))
.then(response => this.userInfoPack = response );
}
getUser(body: User){
this.userJson = JSON.stringify(body);
this.loginservice.postUser(this.userJson)
.subscribe(response => this.userInfo= response);
return this.userInfo; //subscribe method ends here
}
this is the template I wanna pass data into. I want to use #input
to pass the data but I dont know how.
table.component.ts:
template: `
<h2>Hi, {{userInfoPack}}</h2>
`
Please help me with this, thank you!
Also is there any way that we can route the page to another component, since i use a button to route the page and send http.post request to authenticate account at the same time.
<button type= "button" class="btn btn-primary btn-lg"
(click)= "postUser(user)" style=" position: relative;
left: 90%;" routerLink= '/whoison'>
Login
</button>
I don't how to route the component after the credentials are sent and authenticated. (can we use routeLink in a function instead of a directive? )
Any suggestion or advice will be helpful, thank you!

Best solution would be create a sharing service to maintain your data. I had a similar challenge in one of my Angular application. Created a sharingService directive/class and injected/imported into both controller. Now due to singleton nature of services once you update data from one controller it could be fetched/read from other controller.
A good example with Angular 2 :
Bootstrapp your shared service into your application:
bootstrap(AppComponent, [ SharedDataService ]);
Do not add it again in providers attribute of your components. This way you will have a single instance of the service for the whole application.
#Component({
selector : "selectorName",
// providers : [SharedDataService ], //---- not needed
template : `I'm MyComponent and shared data is: {{data}}`,
})
export class MyComponent implements OnInit{
(...)
}

I found an easy way to do this.
We can use local storage if you only want to share string variable.
e.g.
app.component.ts
localStorage.set('name', 'yeehaah');
other.component.ts
var name = localStorage.get('name');
This way, you can access the name variable 'yeehaah' in 'other.component' from 'app.component'
Welcome to elaborate on this!

Related

Is it possible to pass dynamic data into an Angular routing module?

I have to pass data to a route (Angular v12.0.1):
// my-module-routing.module.ts
const routes: Routes = [
{ path: '', component: MyComponent, data: { key: '-> THIS HAVE TO BE DYNAMIC <-' } }
]
This dynamic data have to be passed through the call of the component, can be in HTML with RouterLink, or in TS with navigateWithHistory(), but what have to change is the data inside Route.
I don't know if this is possible, if isn't, is there alternatives?
You can use resolvers to load dynamic data.
The Angular documentation has a good example showing how to fetch data from a service, but you can return any data.
Then you can get the data inside the component this way:
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.data = this.route.snapshot.data;
}
Important to notice that the code on the resolver is going to run before the component is loaded.

What is a proper way to preserve parameters after navigating back?

In my Angular app, I have list and details pages and I want to lkeep the pageIndex value before navigating to details page. There is a Back button in the details page and I can return the list page by clicking on that button. However, I want to get the pageIndex value when navigating back to the list page and let the user open the page where he/she was before. For example I navigate 3rd page on the list and click details. At this stage I set the pageIndex to 3 and then navigate to details. Then by clicking the Back button I can navigate back to the list page, but I need to retrieve the pageIndex as 3.
So, what is an elegant way to fix this problem in Angular 10+?
list-componnet
details(id) {
this.router.navigate(['/details'], { state: { id: id } }); // I pass id value of the record
}
details-componnet
constructor(private router: Router) {
this.id = this.router.getCurrentNavigation().extras.state.id;
}
back() {
this._location.back();
}
Just write a simple example to make it work, I use the sessionStorage and router together, use router to show your the routing module, actually you can just use sessionStorage only, and wrapper it in a servive. Then you can retrieve the pageIndex anywhere.
And if you want to use router only, the pageIndex paramater will be place in both list and detail component, since this two component all need to use this value, in list component you will need pageIndex to set data-table, in detail component you need this value to pass to list component when redirect back triggered.
The routing module like below:
import { NgModule } from "#angular/core";
import { Routes, RouterModule } from "#angular/router";
import { ListComponent } from "./list/list.component";
import { DetailComponent } from "./detail/detail.component";
const routes: Routes = [
{ path: "", redirectTo: "list", pathMatch: "full" },
{
path: "list/:pageIndex=1",
component: ListComponent,
pathMatch: "full"
},
{
path: "detail/:id",
component: DetailComponent
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Here you can navigate to list page from detail page use:
this.router.navigate(["/list/" + pageIndex]);
And then in list page's ngOnInit method to set current pageIndex to your data-table. Here is the demo: https://stackblitz.com/edit/angular-ivy-5vmteg?file=src/app/list/list.component.ts
Use sessionStorage, a listService or router queryParams to keep track of the current pageIndex.
I´d advocate for queryParams as it seems most logical and you can also link directly to specific page.
constructor(
private route: ActivatedRoute,
) { }
// Access the queryParam in list component
// Ie /list?pageIndex=4
this.route.queryParams.subscribe(params => {
// Do something with params.pageIndex if it exists
}
);
I´d also consider to change the way you handle routing to the details. If the route to the list is /list then route to the details should be /list/<listid> so you can link directly to the details if needed.
You can access the listId parameter as below but note it must also be specified as parameter in the router definition.
// Router definition
{ path: 'list/', component: ListComponent},
{ path: 'list/:listId', component: ListIdComponent}
// Access the listId in the details component
this.route.params.subscribe(param=> {
// Do somthing with param.listId
});

How to specify certain method on component from route file in angular 2 & 4?

I have been searching for certain feature we use in Laravel to specify where the routes will go in controllers methods like so:
Route::get('/user', 'UserController#index');
In above code when user navigate to /user the app will send him to the UserController and directly into index method.
I'm looking for something similar to help me handle delete routes because I want to restrict them for super admins only and don't want to write additional component for that
You need to use middleware for that. For example:
Route::get('user/{id}/delete', 'UserController#delete')->middleware('superadmin');
AFAIK this is not possible directly with Angular where as you can do this
Send some data with the router like
{path : 'heroes', component : HeroDetailComponent, data : {some_data : 'some value'}}
And in the component onInit check for the data using ActivatedRoute
and assign it to the proper method of the compoenent
Update
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.sub = this.route
.data
.subscribe(v => console.log(v));
}

angular's equivalent to angularjs states

In AngularJS, for routing purposes, we define states with children which makes it possible to switch between views with the result that each view is always rendered in one container:
$stateProvider.state("communication.create-message", {
url: ...,
templateUrl: ...,
menu: ...,
parent: "communication",
ncyBreadcrumb: {
label: "Create Message"
}
});
Whichever state we choose - the view is always rendered within one container that has ui-view attribute.
I'm trying to achieve the same in Angular 2 or above, but I have no idea of how to reproduce the above-stated functionality.
In app.component.ts we have router-outlet where component templates get rendered.
Say, we have many nested child routes - is it possible to render all of them within this outlet ?
What would the code in app-routing.module.ts look like in this case ?
Could anyone please give a working example of how to go about it ?
Step 1 : Import Routes from #angular/router
in app.module.ts .. import Routes. You have to write
import {Routes} from '#angular/core'
Step 2 :
Add all the routes you want to set up in an array pf type Routes like :
this is for informing angular all the routes in your app. Each route is a javascript object in this array.
const appRoutes : Routes = [
{path : 'communication-route'}
]
always you have to give path , this what you enter after your domain like "localhost :4200/communication-route".
Step 3: Add the action to route i.e what happens when this path is reached.
const appRoutes : Routes = [
{path : 'communication-route'} , component :communicationroutecomponent
]
here i have given the component name "communicationroutecomponent" , i.e this component will be loaded when the path "/communication-route" is reached
Step 4: Register your routes in your app
To do this you will have to do new import in app.module.ts
import {RouterModule} from '#angular/router'
Routermodule has special method forRoot() which registers our routes .
In our case we will have to write this piece of code in
imports :[
RouterModule.forRoot(appRoutes)
]
Our routes are now registered and angular knows our routes now.
Step 5 : Where to display the route content i.e the html content of you route page.
For this angular has directive .
We need to include where we want to load our content i.e in the html.
<a router-outlet="/communication-route"></a>
Navigating to routes :
angular gives a directive for this routerLink
so if we want to navigate to users component , you can give this in your html element:
routerLink="/communication-route"
I hope i was able to explain how this works.
Your code should be as follows
export const ComRoute: Routes = [
{
path: 'communication-route',
children: [
{ path: 'communication', component: communicationComponent },
{ path: ':child1', component: child1Component },
{ path: ':child1/field', component: child1Component}
]
}
];
First of all, states are not an official AngularJS concept. They come from ui-router, which began life as an alternate to the simplistic built in router.
Eventually, ui-router became the de facto standard for routing in AngularJS while the official ng-route module was extracted into a separate, optional library by the Angular team.
ui-router, is complex but exceptional and has earned what is in my view well deserved popularity and success. This success has led to its expansion to support additional platforms enabling the same powerful state based structure in applications written for frameworks such as React.
It is now available for Angular (formerly known as Angular 2).
You can read the documentation and see how to get started on https://ui-router.github.io/ng2
Here is a snippet from the src/app/app.states.ts module of the official example repository
export const loginState = {
parent: 'app',
name: 'login',
url: '/login',
component: LoginComponent,
resolve: [
{ token: 'returnTo', deps: [Transition], resolveFn: returnTo },
]
};
As we can see, there are compatible concepts available, including what looks like a nice evolution of the resolves API which allows function oriented injection which was generally simpler than class based injection in ui-router with AngularJS.
Note, I have not used it in an Angular project.

Angular 2 Shared Data Service is not working

I have built a shared data service that's designed to hold the users login details which can then be used to display the username on the header, but I cant get it to work.
Here's my (abbreviated) code:
// Shared Service
#Injectable()
export class SharedDataService {
// Observable string source
private dataSource = new Subject<any>();
// Observable string stream
data$ = this.dataSource.asObservable();
// Service message commands
insertData(data: Object) {
this.dataSource.next(data)
}
}
...
// Login component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class loginComponent {
constructor(private sharedData: SharedDataService) {}
onLoginSubmit() {
// Login stuff
this.authService.login(loginInfo).subscribe(data => {
this.sharedData.insertData({'name':'TEST'});
}
}
}
...
// Header component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class headerComponent implements OnInit {
greeting: string;
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.data$.subscribe(data => {
console.log('onInit',data)
this.greeting = data.name
});
}
}
I can add a console log in the service insertData() method which shoes the model being updated, but the OnInit method doesn't reflect the change.
The code I've written is very much inspired by this plunkr which does work, so I am at a loss as to what's wrong.
Before posting here I tried a few other attempts. This one and this one again both work on the demo, but not in my app.
I'm using Angular 2.4.8.
Looking through different tutorials and forum posts all show similar examples of how to get a shared service working, so I guess I am doing something wrong. I'm fairly new to building with Angular 2 coming from an AngularJS background and this is the first thing that has me truly stuck.
Thanks
This seems to be a recurring problem in understanding Angular's dependency injection.
The basic issue is in how you are configuring the providers of your service.
The short version:
Always configure your providers at the NgModule level UNLESS you want a separate instance for a specific component. Only then do you add it to the providers array of the component that you want the separate instance of.
The long version:
Angular's new dependency injection system allows for you to have multiple instances of services if you so which (which is in contrast to AngularJS i.e. Angular 1 which ONLY allowed singletons). If you configure the provider for your service at the NgModule level, you'll get a singleton of your service that is shared by all components/services etc. But, if you configure a component to also have a provider, then that component (and all its subcomponents) will get a different instance of the service that they can all share. This option allows for some powerful options if you so require.
That's the basic model. It, is of course, not quite so simple, but that basic rule of configuring your providers at the NgModule level by default unless you explicitly want a different instance for a specific component will carry you far.
And when you want to dive deeper, check out the official Angular docs
Also note that lazy loading complicates this basic rule as well, so again, check the docs.
EDIT:
So for your specific situation,
#Component({
providers: [SharedDataService] <--- remove this line from both of your components, and add that line to your NgModule configuration instead
})
Add it in #NgModule.providers array of your AppModule:
if you add it in #Component.providers array then you are limiting the scope of SharedDataService instance to that component and its children.
in other words each component has its own injector which means that headerComponentwill make its own instance of SharedDataServiceand loginComponent will make its own instance.
My case is that I forget to configure my imports to add HttpClientModule in #NgModules, it works.

Categories