Access element over the router-outlet Angular 6 - javascript

<side-nav [navTitle]="navTitle"></side-nav>
<router-outlet>
</router-outlet>
I have navigation bar at the root component. I created [navTitle] with #Input Decorator inside the side-nav component. side-nav component is placed in another component(root-component). However I want access [navTitle] and change from component which loaded inside the router-outlet acording to which component is loaded. How do I achieve that?

You can't pass any data to router-outlet as to regular component (at the current version of Angular it's not possible, may be it will be added in the future), so the following syntax is invalid:
<router-outlet [dataToPass]="'something'"></router-outlet>
In provided case, you can use services to share data between your components, and I think, that using observable is the best way, because you will get the updated version of data realtime:
data.service.ts
// Other service stuff
#Injectable()
export class DataService {
private navTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('Default nav title');
public setNavTitle(newNavTitle: string): void {
// Sets new value, every entity, which is subscribed to changes (`getNavTitle().subscribe(...)`) will get new value every time it changes
this.navTitle$.next(newNavTitle);
}
public getNavTitle(): Observable<string> {
// Allow to `subscribe` on changes and get the value every time it changes
return this.navTitle$.asObservable();
}
}
side-nav.component.ts
// Other component stuff
export class SideNavComponent implements OnInit, OnDestroy {
public navTitle: string = '';
private getNavTitleSubscription: Subscription;
constructor(private _dataService: DataService) { }
ngOnInit() {
// Will update the value of `this.navTitle` every time, when you will call `setNavTitle('data')` in data service
this.getNavTitleSubscription = this._dataService.getNavTitle()
.subscribe((navTitle: string) => this.navTitle = navTitle);
}
ngOnDestroy() {
// You have to `unsubscribe()` from subscription on destroy to avoid some kind of errors
this.getNavTitleSubscription.unsubscribe();
}
}
And any component, which is loaded in that router-outlet:
any.component.ts
// Other component stuff
export class SideNavComponent implements OnInit {
private navTitleToSet: string = 'Any title';
constructor(private _dataService: DataService) { }
ngOnInit() {
// Set title from current component
this._dataService.setNavTitle(this.navTitleToSet);
}
}
In such case you don't really need to pass the value from root component to side-nav, because you already have a subscription in side-nav component and you will have access to the latest value. If you need navTitle in both root and side-nav components, you can just move the logic with subscription to root.
And here is the working STACKBLITZ.

You can use a service to communicate between components. I have created a short example which would give you a glimpse of how it can be done.
The service being a singleton, has only one instance and hence the properties remain the same.
Hope it helps.
https://stackblitz.com/edit/angular-paziug?file=src%2Fapp%2Fapp.component.ts

Related

Bug Child component input to patch reactive form does not update on Oninit

I got data in ngrx entity store that's gets displayed in chunks according to pager, my problem is rxjs some how its remembering the paging for example I get first page data from server it works then if I get the next page it loads it fine now if I go back to first page the data is truncated by switchMap and if I go front one page once more switchMap gives me an empty array when there is data in the store...After this am totally confused from rxjs...
here is the code I also show when creating new array everything works I just don't understand this strange effect why cause of immutable data? also is
I use ngrx to load a saved state for an input in a child component on Oninit I patch the form with the saved state then I set a listener on the form so each time input changes I save it. The problem in development it works just fine but in production it fails to patch input am guessing loading the data takes a longer time after Oninit has already ran.
Whats the best way to handle this scenario, tried diffrent metthods some give an error cause data modified before view rendered another gets me in endless loop. Here is child Code
export class ChildComponent implements OnInit {
#Output() updateFilters = new EventEmitter<string>(false);
#Input() filters: MessageFilter;
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = fb.group({ kind: [] });
}
ngOnInit() {
if (this.filters.kind) {
this.form.patchValue({kind: this.filters.kind});
}
this.form.valueChanges.pipe(
untilDestroyed(this),
distinctUntilChanged(),
).subscribe(values => {
this.updateFilters.emit(values.kind);
});
}
}
How about using OnChanges instead? Checking the change states / values of your #Input() filters
import { OnChanges, SimpleChanges } from '#angular/core';
#Component({...})
export class ChildComponent implements OnInit, OnChanges {
...
#Input() filters: MessageFilter;
ngOnChanges({ filters }: SimpleChanges) {
console.log(filters); // if you want to check any states/activities
if (filters && filters.currentValue && filters.currentValue.kind)
this.form.patchValue({ kind: this.filters.kind });
}
ngOnInit() {
this.form.valueChanges.pipe(
untilDestroyed(this),
distinctUntilChanged(),
).subscribe(values => this.updateFilters.emit(values.kind));
}
}

Angular 2 service calls method from component

Is it even possible to let a service call an component Method?
myapp.component
export class MyAppComponent {
public value;
...
public setValue(payload){
this.value = payload;
}
}
myapp.service
#Injectable()
export class MyAppService {
private myAppComponent: MyAppComponent;
private apiClientService: ApiClientService
// ...
After i make an PUT http call, the body from the response is my new "value"
// ...
putValue(payload: JSON){
return this.apiClientService.putAPIObject(payload).then((response) => {
this.myAppComponent.setValue(response);
});
}
}
This results in an ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'setValue' of undefined.
Can someone explain what im doing wrong?
Thanks in advance.
EDIT:
Since people complain about my approach, im totally fine to start from scratch if someone can explain me what is the best way to handle this problem.
I get values from an api, change them and i put them back to the api. I dont want to make a get call again, so i get the new data i want in the response of the Put call.
The call goes from component --> component service --> apiclient service
I guess the problem is that i have an extra service between the start and end point.
EDIT 2: I tried to avoid the component service and maked it work for me with only component --> apiclient service
Even this soultion is working for me at the moment I kind of dislike it, because I have to Copy and Paste a lot of code for the Same "Operation" with other objects from my api. For example I maked it work for the Picture Component, but I also need this for my Movie Component. Usally its a bad thing if I write the same code often in a project, or not?
There are at least a couple ways to solve this, but hopefully this gives you a start. Open to feedback and corrections.
Use an Observable
Let the service own knowledge of the value changes and emit changes. The component listens to an EventEmitter on1 the service to react to value changes. (See also: Creating and returning Observable from Angular 2 Service)
MyAppService
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MyAppService {
private valueSource = new Subject<any>();
public valueUpdate$ = this.valueSource.asObservable();
putValue(payload: JSON){
return this.apiClientService.putAPIObject(payload).then((response) => {
/** here **/
this.valueUpdate$.next(response);
});
}
}
MyAppComponent
export class MyAppComponent {
public value;
private valueSubscription;
constructor(private _myAppService: MyAppService) {}
ngOnInit() {
/** and here **/
this._myAppService.valueUpdate$.subscribe((p) => this.setValue(p));
}
...
public setValue(payload){
this.value = payload;
}
}
Register the component
Answering the original question, the idea is to register the component with the service so that it can call the component as needed. You could pull a references through dependency injection but wouldn't recommend it (e.g. what if your original component reference is destroyed?)
MyAppService
#Injectable()
export class MyAppService {
private myAppComponent: MyAppComponent;
/** here **/
registerMyApp(myApp: MyAppComponent) {
this.myAppComponent = myApp;
}
putValue(payload: JSON){
return this.apiClientService.putAPIObject(payload).then((response) => {
this.myAppComponent.setValue(response);
});
}
}
MyAppComponent
export class MyAppComponent {
public value;
/** and here **/
constructor(myAppService: MyAppService) {
myAppService.registerMyApp(this);
}
...
public setValue(payload){
this.value = payload;
}
}
Thanks AJT_82 for noting that Angular does not want developers using EventEmitters on the service: What is the proper use of an EventEmitter?.

How to bind an input parameter of an Angular4 component

I have a small web site I am developing in Angular4 (my first attempt in Angular) and have come across an issue I just can seem to figure out. Simplified down my scenario is:
I have a component (account-list) which using a html5 select/option control which is populated from a rest api and displays a list of accounts.
I have a second component which displays the details of the account (account-detail) and take accountId as an input parameter.
When an account is selected in the account-list component I want the account-detail to auto update to the newly selected account. I know that my account-list component is working fine and that when I select an account the selectAccountId variable in the ts is being updated.
However I just can't seem to get the update to the selectAccountId variable to trigger an update to the account-detail component. I know this component works fine on it own as a default account is displayed and the id of this default is set in the ngOnInit method of the account-list component.
The relevant html5 in the account-list component:
<select id="Id" #Id="ngModel" class="hideLabel form-control" [(ngModel)]="selectedAccountId" name="Id">
<option [ngValue]="account.Id" *ngFor="let account of accounts">  
{{account.Name}}  
</option>  
</select>  
<!-- this div just to prove that selectedAccountId changes when a new account is selected, which it does -->
<div *ngIf="selectedAccountId">
{{selectedAccountId}}
</div>
<!-- this is the line which does not seem to work, changes to selectedAccountId are not triggering the component to refresh -->
<account-detail *ngIf="selectedAccountId" [account]="selectedAccountId"></account-detail>
The ts code of the account-list component:
export class AccountListComponent implements OnInit {
selectedAccountId: number;
title: string;
accounts: AccountHeader[];
errorMessage: string;
constructor(private accountService: AccountService, private router: Router) { }
ngOnInit() {
var s = this.accountService.getLatest();
s.subscribe(
accounts => {
this.accounts = accounts; this.selectedAccountId = this.accounts[0].Id;
},
error => this.errorMessage = <any>error
);
}
}
The ts code of the account-detail component:
export class AccountDetailComponent {
#Input("account") selectedAccountId: number;
account: Account;
selectedLicense: License;
constructor(
private authService: AuthService,
private accountService: AccountService,
private router: Router,
private activatedRoute: ActivatedRoute) {
}
ngOnInit() {
if (this.selectedAccountId) {
this.accountService.get(this.selectedAccountId).subscribe(
account => this.account = account
);
}
else {
this.router.navigate([""]);
}
}
}
In all honesty I've lost track of the things I've tried to make this work, most of the blogs, guides etc I've read talk about how to bind the other way and I have all that working just fine. But I can't find how to get the binding to trigger an update of the account-detail component which should be accomplished by this line:
<!-- this is the line which does not seem to work, changes to selectedAccountId are not triggering the component to refresh -->
<account-detail *ngIf="selectedAccountId" [account]="selectedAccountId"></account-detail>
I've been following a book in which this was working fine, but originally this was working in AngularJS and then migrated to Angular2 (then 4) and somewhere along the way this has stopped working.
Any help would be much appreciated, thanks!
So in your account-detail component you are using the ngOnInit method to fetch some data depending on your selected account id. ngOnInit is a lifecycle hook that is called once when a component is created. When you change the selected id, angular is not recreating the component and the method does not fire.
What you need is a method that fire when you change the variable. There are several approaches you can use, check out this comprehensive post for more information.
Here is a simple example using a property setter to trigger a method call when the input is changed:
export class AccountDetailComponent {
selectedAccount: Account;
private _selectedAccountId: number;
#Input("account") set selectedAccountId(value: number) {
this._selectedAccountId = value;
this.getAccount();
}
getAccount() {
if (this._selectedAccountId) {
this.accountService.get(this._selectedAccountId).subscribe(
account => this.selectedAccount = account
);
}
}
}

draft state/publish changes on save in angular 2

I'm using angular 4 in my application and currently the user is able to make changes over multiple components(drag and drop, remove items, add items and etc...).
Now, for every user action there is a http requests via the relevant service that persist the changes on the DB.
There is a requirement that the user will be able to make this changes and only persist them once he done changing and pressed save. (it can be 10-50 actions from different types.)
How would you suggest refactoring the code in order to support that ? to make an array of user actions, and on save iterate over the array and make the relevant actions one by one, write some http middleware to hold all http calls until 'save' is pressed?
You should look into redux. This would allow a MVP programming model and works just fine with Angular. ng2-redux
Just so you know the problem you are facing has a name.
"Application State Management"
This can be solved via redux like libraries (redux/ rxjs-store rxjs-effects etc)..
Or you could just use plain rxjs BehaviourSubject or Subject as Observable.
here is a plunker example of using plain rxjs observables and angular services to achieve state management.
https://embed.plnkr.co/dEDJri4TziCS91oZiuHb/
TL;DR
This is the services
#Injectable()
export class AppStateService{
private _dataSaved = new Subject<string>();
public dataSaved$ = this._dataSaved.asObservable()
constructor() {}
dispatchSaveEvent(data: String){
this._dataSaved.next(data);
}
}
This is the component that will dispatch the save event
#Component({
selector: 'my-footer',
template: `
<button (click)="saveData($event)">Save</button>
`
})
export class Footer implements OnInit {
constructor(private appState: AppStateService) {}
ngOnInit() {}
saveData(e){
this.appState.dispatchSaveEvent("Some data to save here...");
}
}
This is how you consume the observable in every component that is interested that a saved has occurred
#Component({
selector: 'my-comp-1',
template: `
<h1>Component-1! {{savedDataRecived}}</h1>
`
})
export class Comp1 implements OnInit {
savedDataRecived = "";
constructor(private appState: AppStateService) {}
ngOnInit() {
this.appState.dataSaved$.subscribe(data=> this.handleSaveEvent(data))
}
handleSaveEvent(data: string){
this.savedDataRecived = data;
}
}

Inject data into component in Bootstrap Modal on Angular 4

I'm using the #ng-bootstrap/ng-bootstrap which adds Bootstrap components to Angular. You can inject components into an opened Modal, so the injected component makes up the body/content of the opened modal.
I have a list of Player. When I click a player, I want to open a Modal with the clicked player's data inside of it. I can open a Modal with the injected PlayerComponent like this.
constructor(
private modalService: NgbModal
) { }
openPlayerModal() {
const modalRef = this.modalService.open(PlayerComponent)
}
The question is... How do I inject additional data to the component to let it know what player's data to fetch? E.g. I might have an OnInit interface on the PlayerComponent that fetches data from an API based on an ID supplied by the modalService.open() call.
It seems like #ng-angular/ng-angular allow you to touch the component instance after it has been instantiated. So you can then modify the values.
So solution would be:
constructor(
private modalService: NgbModal
) { }
openPlayerModal() {
const modalRef = this.modalService.open(PlayerComponent)
modalRef.componentInstance.player_id = 1;
}
And then make the component use an #Input() decorator on player_id.
export class PlayerComponent {
#Input() player_id;
...
}

Categories

Resources