draft state/publish changes on save in angular 2 - javascript

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;
}
}

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));
}
}

Service side Obsevable data does not update initially in component Angular

I am fairly new to Angular and I'm trying to display async data from my service into my component. When I do this, the data seems to update, but the component only shows the new data after I click a button. It seems to me the DOM is not updated when data is changed in my service, and only updates when I tell it to, in this example on the click of a button.
My app.component.ts :
import { Component } from '#angular/core';
import { AuthserviceService } from './services/authservice.service';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
displayName: string = 'no displayName available';
subscription: Subscription;
constructor(public authService: AuthserviceService){}
ngOnInit(){
this.subscription = this.authService.getDisplayName().subscribe(displayName => {this.displayName = displayName})
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
login(){
this.authService.login();
}
logout(){
this.authService.logout();
}
methodThatDoesNothing(){
}
}
My app.component.html :
<button (click)="logout()">Logout</button>
<button (click)="login()">Login</button>
<button (click)="methodThatDoesNothing()">button that does nothing</button>
<p>{{ displayName }}</p>
<router-outlet></router-outlet>
My authservice.service.ts :
import { Injectable } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/auth';
import * as firebase from 'firebase/app';
import { Observable, Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class AuthserviceService {
private displayName = new Subject<any>();
constructor(public afAuth: AngularFireAuth) { }
login() {
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then(data => {
this.displayName.next(data.user.displayName);
});
}
logout() {
this.afAuth.auth.signOut();
}
getDisplayName(): Observable<any> {
return this.displayName.asObservable();
}
}
I am trying to authenticate by using Google (popup shows up, I login, data is provided by Google (including displayName) ). I then want to display my display name, which is saved in the service, into my component. Before logging in, this is what I see:
When I click login, I can login, but the displayname is not updated. Only when I click the button "button that does nothing" (or any other button), the display name is updated:
It seems to me the DOM is changed on the click of a button, but I don't really know what is actually happening behind the scenes ( I coulnd't find the answer, mainly because I did not know what to look for).
Am I doing something wrong in my code?
Am I forgetting something?
How would I correctly display the displayName, after logging in?
Thanks in advance.
EDIT:
This piece of code is the problem I figured, I don't know why yet:
login() {
this._displayName.next('this data will be shown on the template immediately');
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then(data => {
this.displayName.next(data.user.displayName); //This data here does not
});
}
The first "displayname.next" changes the displayName and immediately changes the value on csreen.
The second one, in the popup method, does not show immediately, and requires me to click a button (which forces a refresh???) to show the displayname.
I would recommend that you use BehaviorSubject instead of Subject.
A BehaviorSubject holds one value. When it is subscribed it emits the value immediately. A Subject doesn't hold a value.
private displayName: BehaviorSubject<string> = new BehaviorSubject('no displayName available');
create a get function
get _displayName(){
return this.displayName
}
subscribe it in the template with async pipe
<p>{{ authService._displayName | async }}</p>
when you want to pass a new value to it just use
_displayName.next('new value')
or if outside of service
authService._displayName.next('new value')
async pipe will handle subing and unsubscribing for you.
So you can remove the subscription on OnInit.
Ive made a stackblitz example that comes close to yours to showcase this
https://stackblitz.com/edit/angular-effvqv
The way you did it was a bit off becouse
Observable → Subject → BehaviorSubject
so you make an Observable out of an Observable.
Hope this helped.
I have "fixed" the problem by forcing it to detect a change. I first tried ChangeDetectorRef, but that is only a solution when used inside of the component, while using a service, I used the solution mentioned in this topic (which uses ApplicationRef):
Trigger update of component view from service - No Provider for ChangeDetectorRef

I want to create a custom event which can trigger from any component and get listened to any component within my angular 7 app

I want to create a custom event which can trigger from any component and get listened to any component within my angular 7 app
Suppose I have 1 component in which I have a button on click on which I want to trigger my custom event with some data. Next, there will be another component which will constantly listening for that event when it triggers it will execute some code and update the ui accordingly.
How should I implement it?
Well, well, well, what you're looking for is a Shared Service. This shared service will have a BehaviorSubject that will act as a source for the data. Through this, you will be able to push new data streams. And then you will expose this BehaviorSubject asObservable.
You will then subscribe to this Observable from all the components where you want to listen for data changes and then react accordingly.
This is what this is going to look like in code:
import { Injectable } from '#angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
#Injectable()
export class SharedService {
private data: BehaviorSubject<any> = new BehaviorSubject<any>(null);
data$: Observable<any> = this.data.asObservable();
constructor() { }
setData(newData) {
this.data.next(newData);
}
}
You can now inject the SharedService in any controller you want and call setData from the component that you want to push new data from(see the AppComponent from the Sample StackBlitz for more details). And then you'll also be injecting the SharedService in other components and in there, you'll subscribe to data$ in their ngOnInit(see the HelloComponent from the Sample StackBlitz for details)
Here's a Sample StackBlitz for your ref.

Access element over the router-outlet Angular 6

<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

Angular 2 transfer ajax call response to another component

I just started playing with angular 2 and i've ran into a small problem, that i ve searched for in various forms and also angulars documentation.
I've managed to make a service that makes a call and then i want in a component when i press a button to load another component with dynamicload component and have access to the ajax result.
The problem is that I can t figure out how to do that..
The question is how can I make the result accesible in other components using Observables or Promises method.
If I understood correctly your question, you are looking a way to insert a data from request to another nested component.
I hope this image will clarify for you the data flow for this case.
Your Root component is calling a service method which returns for you promise object.
Then you map needed data from response to the component model inside Root Component constructor.
And your Child component should be subscribed for the model which you was preparing in previous step.
ngOnInit() {
this.dataService.getSomeData()
.subscribe((data: IData) => {
this.data = data;
});
}
Just a short example above how to set model in the root component from the promise object to the local model.
New research:
There is another way to fill your components by data from api's. You can use EventEmitter to emit event from service, and then, you can subscribe for this event inside you created components, so they will get a data, each time there will be called the service. Here is nice example of this strategy in the first answer. Service Events
Hope it will help you, let me know if you will need additional info!
Just create a service, then inject the service where you want.
Here it's an example how to share a service ajax data across many components without making the request twice :
https://stackoverflow.com/a/36413003/2681823
the Service:
#Injectable()
export class DataService {
constructor(private http: Http) { }
private _dataObs = new ReplaySubject<request>(1);
getData(forceRefresh?: boolean) {
// On Error the Subject will be Stoped and Unsubscribed, if so, create another one
this._dataObs = this._dataObs.isUnsubscribed ? new ReplaySubject(1) : this._dataObs;
// If the Subject was NOT subscribed before OR if forceRefresh is requested
if (!this._dataObs.observers.length || forceRefresh) {
this.http.get('http://jsonplaceholder.typicode.com/posts/2')
.subscribe(
requestData => {
this._dataObs.next(requestData);
},
error => this._dataObs.error(error));
}
return this._dataObs;
}
}
the Component:
#Component({
selector: 'child',
template : `<button (click)="makeRequest()" class="btn">Click me!</button>`
})
export class Child {
constructor(private _dataService: DataService) { }
makeRequest() {
this._dataService.getData().subscribe(
requestData => {
console.log('ChildComponent', requestData);
}
}
}
A full working example/plunker can be found here : http://plnkr.co/edit/TR7cAqNATuygDAfj4wno?p=preview

Categories

Resources