Unable to get variable outside subscribe in Angular - javascript

Well, maybe my issue sounds similar to the few questions here about Angular2-EventEmitter-Subject-Observable-Subscribe-stuff... but it's quite different.
My goal (in short):
Be able to go from route ItemsList to route AddNewItem and then push on the custom 'back' button and find myself in ItemsList WITH ALL SETTINGS THAT I HAD HERE. For a example - search params. So... the only way (afaik) in Angular2+ to store that params between non-related components is to use shared service. So... My code:
ItemsList ts\html (very briefly):
export class ListComponent implements OnInit {
public addItem(allSettings: ISettingsTable) {
this._shService.returnToList = true;
this._shService.allCurrentSettings = allSettings;
this._router.navigate(['someRoute', 'add']);
}
ngOnInit() {
this._settings = {
search: /* .. */,
/* .. */
}
this._shService.getSettings().subscribe((settings: ISettingsTable) => {
this._settings = settings;
}
}
}
<button (click)='addItem(_settings)'>
<mat-icon svgIcon='add'></mat-icon>
</button>
Service (in short):
export class SettingsService {
private _returnToList: boolean;
private _settingsChanged$: Subject<ISettingsTable> = new Subject();
public emitSettings(val: ISettingsTable) {
this._settingsChanged$.next(val);
}
public getSettings(): Subject<ISettingsTable> {
return this._settingsChanged$;
}
}
AddEditItems ts\html (very briefly as well):
export class EditComponent implements OnInit {
public back(par: ISettingsTable): void {
if (this.returnToList) {
this._router.navigate(['someRoute', 'list']).then(() => {
this._shService.emitSettings(par);
});
ngOnInit() {
if (this._shService.returnToList) {
this.returnToList = true;
this._p = this._testS.allCurrentSettings;
}
}
}
<button mat-raised-button (click)='back(_p)'>Go back</button>
So! When I go to the Add route I store the current params in service. Fine. Then on the Add route I get them from service. Fine. Then when I wanna go back I pass that params to the emit method of Subject\Event Emitter... Fine! But on the List route this._settings exists only INSIDE subscribe... It's undefined outside. Yeah, I know about asynchronous behavior and stuff... But how to resolve my issue properly??? Anyone! Please. Hope I made myself clear.
EDIT...
Obviously I always on all routes have some settings. The issue is I dunno how to code If I've got some settings from observable - use them, if not - use default this._settings ...

Related

angular material dialog pass dynamic function when clicked on save

dialog.ts
export class DialogComponent {
constructor(private service: RestService, private dialogRef: MatDialogRef<DialogComponent>) {
}
no() {
this.dialogRef.close();
}
save() {
const url = 'https://gorest.co.in/public-api/users';
const options = {headers: HttpHeaders, params: HttpParams};
const getResp: any = this.service.Get(url, options);
this.dialogRef.close();
///THIS save() FUNCTION HARDCODED, BUT I WANT TO MAKE RESTCALL DYNAMICALLY WHEN ANYONE CLICKS SAVE BUTTON IN DIALOG
}
}
dialog.html
<mat-dialog-actions>
<button class="mat-raised-button"
(click)="no()">
no
</button>
<button class="mat-raised-button mat-primary"
(click)="save()">
save
</button>
</mat-dialog-actions>
component1.ts
getUsers() {
const dialogConfig = new MatDialogConfig();
const dialogRef = this.dialog.open(DialogComponent,
dialogConfig, passSomeRestCall-1-FunctionSomehow());
}
component2.ts
getEmployees() {
const dialogConfig = new MatDialogConfig();
const dialogRef = this.dialog.open(DialogComponent,
dialogConfig, passSomeRestCall-2-FunctionSomehow());
}
Above 2 components have to make use of Dialog Component dynamically, currently above save() hardcoded with some restcall, but actually rest call needed when clicked on save() for both above components. So, in short, save() button should happen dynamic restcall based on the component.
Could anyone please help me on above, I am very new to angular.
EDIT:
for (let i = 0; i < this.items.length; i++) {
this.service.makeRestCall(this.items[i]);
}
How do I pass above for loop logic in dialog component? I should be able to do some business logic dynamically inside save() based on component like below
save(){
dynamicMethod() // this should be passed somehow from a component
this.dialogRef.close();
}
You do not need to pass the callback method. It should be part of modal's parent.
Only you have need is to set #Output into the modal with type EventEmitter. Like:
#Output() save = new EventEmitter<boolean>();
On Save button just implement:
handleSave: void {
this.save.emit(true);
}
Parent will listen this #Output and handle it properly.
dialogRef.componentInstance.save$.subscribe((res) => {
// Here is your business logic
}
);
So:
If after processing is everything OK, you can use dialogRef in your parent and close the modal (dialogRef.close()).
If something is wrong, modal will not be closed.
Using this way, our modal will be free of business logic. Especially important for generic modals, like confirmation, user inputs, ...
You can pass your url , options etc as data in your dialog component and pass it in save function like below.
UserComponent
openDialog(): void {
let ApiDetails = {
url: 'https://gorest.co.in/public-api/users'
}
const dialogRef = this.dialog.open(DialogComponent,
{
data: ApiDetails
});
}
DialogComponent
export class DialogComponent implements OnInit {
constructor(
private dialogRef: MatDialogRef<DialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: any) {
}
no(){
this.dialogRef.close()
}
save(){
console.log(this.data) // this will have url and other details whatever you send from calling parent
}
}

httpClient.get is undefined when using dynamic function/observable references

So I asked a question a few days ago and got some headway on a solution, however now I'm stuck at another wall I'm unsure how to get over.
I have two parent components, a shared view/component with an extended base component, and a service all hooked together. The objective is to use the two parent components to drive what data is shown within the shared component. The two parent components use references to service methods passed into the shared component to get the data.
I've reached an issue where my http.get is always undefined no matter what I try. I've instantiated it like I do in my other services but I've had no luck. I suspect this is caused by how i pass in my service references. Code below:
Parent Component Code:
// PARENT COMPONENT
myData$: Observable<myType>;
searchMethod: Function;
constructor(private myService){
this.myData$ = this.myService.myData$;
this.searchMethod = this.myService.searchData;
}
// PARENT COMPONENT HTML
<app-shared-component
[myData$] = "myData$"
[searchMethod]="searchMethod">
</app-shared-component>
Shared Component Code:
export class MySharedComponent extends BaseComponent<MyType> implements OnInit {
#Input() myData$: Observable<myType>;
#Input() searchMethod: Function;
constructor() { super(); }
ngOnInit(): void {
this.data$ = this.myData$;
}
search(): void {
this.searchMethod().subscribe(//do something);
}
Base Component Code:
#Input data$: Observable<T>;
ngOnInit(): void {
this.data$.subscribe((response: T) => //do something);
super.ngOnInit();
}
Service Code:
private myDataSubject = new BehaviorSubject<MyType>(new MyType());
get myData$(): Observable<MyType> {
return this.myDataSubject.asObservable();
}
constructor(private http: HttpClient) { }
searchData(): Observable<void> {
return new Observable<void>(observer => {
this.http.get<MyType>(
'http://myuri'
).subscribe(
response => {
// do something
},
() => observer.error(),
() => observer.complete()
);
});
}
It looks like you're losing the context of your service when you set this.searchMethod = this.myService.searchData in your parent component. It should work if you change searchData() { to an arrow function: searchData = (): Observable<void> => {.

Changing value in one component affects another

I have an angular 6 application, which has a top bar and a content area below this. These are different component and I am currently developing the user profile page. Name of the user is also displayed in the top bar.
My problem is like whenever I have updated the user's name in EditUser page, it successfully saves, but the same is not updated on the top bar. In Vue.js, I can simply handle this with a Vuex store; but how can I handle this in Angular 6.
Any help would be much appreciated.
Next time post a bit of code. Since there isn't, I'll show you how to do it with an example.
Let's assume we have two component, A and B. And the changes will be reflected on both of two components.
Service :
export class YourService{
private data$ = new BehaviorSubject<string>(null);
data = this.data$.asObservable();
public setData(data: string){
this.data$.next(data);
}
}
Component A/B.html :
<div>{{something}}</div>
Component A/B.ts :
isAlive = true;
something: string;
constructor(
private service: YourService
) { }
ngOnInit(){
this.service.data
.takeWhile(() => this.isAlive)
.subscribe( res => {
if(!!res){
this.something = res;
}
});
}
ngOnDestroy(){
this.isAlive = false;
}
Component that change the status:
export class AnotherComponent{
constructor(
private service: YourService
) { }
private changeData(data: string){
this.service.setData(data);
}
}
Now everything is working fine. BehaviorSubject allow the communication between components. whenever the function changeData is fired, you will see the changes on both of your components.
takeWhile is for unsubscribing when the component die.
If you have more question, feel free to ask them to me and I will edit this answer.
You can create service to exchange data between components. It could be UserService that provide access to current user information.
#Injectable()
export class UserService {
user: UserInfo;
// define user here (load from backend or somehow else)
}
In user-profile.component.ts
export class UserProfileComponent {
constructor(public userService: UserService) { }
}
user-profile.component.html
<input type="text" [(ngModel)]="userService.user.name">
In header.component.ts
export class HeaderComponent {
constructor(public userService: UserService) { }
}
header.component.html
<span>{{ userService.user.name }}</span>
So the anggular DI will create a singleton UserService and injects the same object to both components. And when you change it in any of them the changes will be shown in other.

Data sharing between component with data servive not working in angular 5

I, am using data service to share the data between the component. However, this seems not working for me.
Got the reference from here
Angular to update UI from the child component reflect the value to the parent component
https://angularfirebase.com/lessons/sharing-data-between-angular-components-four-methods/
I tried the same logic as above but seems to not work for me.
Here is the html binding for the angular material
<mat-progress-bar mode="indeterminate" *ngIf="commonViewModel.showProgressBar()"></mat-progress-bar>
Parent component
export class AppComponent {
constructor(public commonViewModel: CommonViewModel) { }
ngOnInit() {
this.isLoding();
}
isLoding() {
console.log("app=" + this.commonViewModel.showProgressBar());
return this.commonViewModel.showProgressBar();
}
}
Child Component
export class HomeComponent {
private GetHomeItemUrl: string = "Home/GetHomeItem";
private _homeItemService: GenericHttpClientService;
constructor(public commonViewModel: CommonViewModel) {
this.getHomeItemHttpCall();
}
private getHomeItemHttpCall(): void {
this.commonViewModel.setProgressBarShow = true;
this._homeItemService.GenericHttpGet<GenericResponseObject<HomeViewModel>>(this.GetHomeItemUrl).subscribe(data => {
if (data.isSuccess) {
this.commonViewModel.setProgressBarShow = false;
console.log("home=" +this.commonViewModel.showProgressBar());
}
}, error => {
console.log(error);
});
}
}
This is my service class which hold the value as true and false
#Injectable()
export class CommonViewModel {
progressBarShow: boolean = true;
public showProgressBar(): boolean {
return this.getProgressBarShow;
}
set setProgressBarShow(flag: boolean) {
this.progressBarShow = flag;
}
get getProgressBarShow(): boolean {
return this.progressBarShow;
}
}
The console output
In the console I,can see the output as True and False. But the app never hides as I can see the app component value is always true
Where I, am doing mistake. Can please someone let me know. I, dont want to use Input and Output to share the data.
Please let me know how can I resolve this issue.
it's possible that your parent component and your child component are being injected with two different instances of the service, depending on where you "provide" it. Try providing it from your app module.
Also, if the child is a direct child of the parent, you don't need the service, you can have an EventEmitter (an #Output) in child, and communicate through that.
See the documentation at https://angular.io/api/core/EventEmitter
I think, that GSSWain's answer must be work. If not, try use a getter
<mat-progress-bar *ngIf="isLoading"></mat-progress-bar>
get isLoading(){
return this.commonViewModel.showProgressBar();
}

how can I listen to changes in code in angular 2?

I'm using angular 2. I have a component with an input.
I want to be able to write some code when the input value changes.
The binding is working, and if the data is changed (from outside the component) I can see that there is change in the dom.
#Component({
selector: 'test'
})
#View({
template: `
<div>data.somevalue={{data.somevalue}}</div>`
})
export class MyComponent {
_data: Data;
#Input()
set data(value: Data) {
this.data = value;
}
get data() {
return this._data;
}
constructor() {
}
dataChagedListener(param) {
// listen to changes of _data object and do something...
}
}
You could use the lifecycle hook ngOnChanges:
export class MyComponent {
_data: Data;
#Input()
set data(value: Data) {
this.data = value;
}
get data() {
return this._data;
}
constructor() {
}
ngOnChanges([propName: string]: SimpleChange) {
// listen to changes of _data object and do something...
}
}
This hook is triggered when:
if any bindings have changed
See these links for more details:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html
As mentioned in the comments of Thierry Templier's answer, ngOnChanges lifecycle hook can only detect changes to primitives. I found that by using ngDoCheck instead, you are able to check the state of the object manually to determine if the object's members have changed:
A full Plunker can be found here. But here's the important part:
import { Component, Input } from '#angular/core';
#Component({
selector: 'listener',
template: `
<div style="background-color:#f2f2f2">
<h3>Listener</h3>
<p>{{primitive}}</p>
<p>{{objectOne.foo}}</p>
<p>{{objectTwo.foo.bar}}</p>
<ul>
<li *ngFor="let item of log">{{item}}</li>
</ul>
</div>
`
})
export class ListenerComponent {
#Input() protected primitive;
#Input() protected objectOne;
#Input() protected objectTwo;
protected currentPrimitive;
protected currentObjectOne;
protected currentObjectTwo;
protected log = ['Started'];
ngOnInit() {
this.getCurrentObjectState();
}
getCurrentObjectState() {
this.currentPrimitive = this.primitive;
this.currentObjectOne = _.clone(this.objectOne);
this.currentObjectTwoJSON = JSON.stringify(this.objectTwo);
}
ngOnChanges() {
this.log.push('OnChages Fired.')
}
ngDoCheck() {
this.log.push('DoCheck Fired.');
if (!_.isEqual(this.currentPrimitive, this.primitive)){
this.log.push('A change in Primitive\'s state has occurred:');
this.log.push('Primitive\'s new value:' + this.primitive);
}
if(!_.isEqual(this.currentObjectOne, this.objectOne)){
this.log.push('A change in objectOne\'s state has occurred:');
this.log.push('objectOne.foo\'s new value:' + this.objectOne.foo);
}
if(this.currentObjectTwoJSON != JSON.stringify(this.objectTwo)){
this.log.push('A change in objectTwo\'s state has occurred:');
this.log.push('objectTwo.foo.bar\'s new value:' + this.objectTwo.foo.bar);
}
if(!_.isEqual(this.currentPrimitive, this.primitive) || !_.isEqual(this.currentObjectOne, this.objectOne) || this.currentObjectTwoJSON != JSON.stringify(this.objectTwo)) {
this.getCurrentObjectState();
}
}
It should be noted that the Angular documentation provides this caution about using ngDoCheck:
While the ngDoCheck hook can detect when the hero's name has changed,
it has a frightful cost. This hook is called with enormous frequency —
after every change detection cycle no matter where the change
occurred. It's called over twenty times in this example before the
user can do anything.
Most of these initial checks are triggered by Angular's first
rendering of unrelated data elsewhere on the page. Mere mousing into
another input box triggers a call. Relatively few calls reveal actual
changes to pertinent data. Clearly our implementation must be very
lightweight or the user experience will suffer.

Categories

Resources