Angular2 - changing model data using Promises - javascript

I am experimenting with Angular2 (with no experience of Angular1), I have used data binding successfully, but I now want to add promises into the mix to simulate an async request for data.
In the following code, setting newData to an object literal works fine, but setting it to an existing populated object does not work.
After trying this for over a day, I need some help.
I'm looking for the simplest way to have my promise return some data and have it update the interpolated value in the template. I've read that zones are sometimes used for this, but at the moment, want to keep this to the bare minimum, to help my understanding (hence I've removed factory classes, interfaces, and dependency injection from the sample code)
I suspect the answer is in this victorsavkin post, but at my current level of understanding, it's beyond me.
http://plnkr.co/edit/amLIZWe5UI9jlvpVGv9V?p=preview
import {Component} from 'angular2/core'
import {MockData} from './mock-data.ts'
#Component({
selector: 'my-app',
providers: [],
template:`
<h1>
Hello {{data.name}}
</h1>`,
directives: []
})
export class App {
public data = {}
constructor() {
new Promise((response, reject) => {
setTimeout(function() {
newData = {name:'Sid'} //works
//newData = MockData //doesn't work
response(newData)
}, 1000)
})
.then(response => this.data = response)
}
}
mock-data.js
exports MockData = {
name:'Nancy'
}

I made these two changes to your plunker to get it to work:
export var MockData = {
name:'Brian'
}
import {MockData} from '../mock-data.ts'
New plunker.
You also don't have to initialize the data property if you use the Elvis operator (?.):
Hello {{data?.name}}
Then in your component just:
public data;

Related

How to use one function for different BehaviorSubjects

I used to have one banner at the top of the page for all events in my app (like some Errors, Warnings, and Success) and used for that BehaviorSubject.
For example:
in the main app.component.html file I had:
<baner [alerts]="alerts$ | async"></baner>
and alerts get from bannerService:
ngOnInit(): void { this.alerts$ = this.bannerService.alerts$; }
the service looks next:
alertSub$ = new BehaviorSubject<string>('');
alerts$ = this.alertSub$.asObservable();
showWarning(message: string): void {
const newAlert = { message, type: 'Warning' };
this.alertSub$.next([...this.alertSub$.getValue(), newAlert]);
setTimeout(() => this.dismiss(newAlert), 500);
}
dismiss(alert): void {
const updatedAlerts = this.alertSub$.getValue().filter(alertSub => alertSub !== alert);
this.alertSub$.next(updatedAlerts);
}
...and so on...
So when I wanted to add some warning, I called this.bannerService.showWarning('some msg') and everything was fine.
But now I need to add a banner inside another component for its own warnings, and it should be independent. This means that global warnings would be still on the top of the app, but warnings of this component are only inside the component.
I understand, that I should create a new BehaviorSubject, but how to re-use all functions correctly?
For now, I've added to all functions a parameter, that pass proper BehaviorSubject, but in that case, I need to make changes in all places, where bannerService was used.
Service with my new changes:
alertSub$ = new BehaviorSubject<string>('');
alerts$ = this.alertSub$.asObservable();
componentSub$ = new BehaviorSubject<string>('');
componentAlerts$ = this.componentSub$.asObservable();
showWarning(message: string, banner: BehaviorSubject<string>): void {
const newAlert = { message, type: 'Warning' };
banner.next([...banner.getValue(), newAlert]);
setTimeout(() => this.dismiss(newAlert, banner), 500);
}
dismiss(alert, banner: BehaviorSubject<string>): void {
const updatedAlerts = banner.getValue().filter(alertSub => alertSub !== alert);
banner.next(updatedAlerts);
}
...and so on...
Would be really grateful for any idea, on how to use old functions for different BehaviorSubjects.
I think it can be a bit easier than that. Your baner component is responsible of rendering the messages, right? What if you modify this component to take in two instances of bannerService instead of just one? Let's suppose this is our BannerComponent:
export class BannerComponent implements OnInit {
bannerService: BannerService;
constructor(
#Host() #Optional() parentBannerService: BannerService,
#Inject() globalBannerService: BannerService
) {
this.bannerService = parentBannerService ?? globalBannerService;
}
This allows us to ask the injector for an (optional) instance of BannerService that is provided by the parent component (the component that renders the BannerComponent component).
In case we don't have such a thing, we still want the BannerService to be injected from somewhere, hence the second parameter, globalBannerService.
Now all that is left for us to do, is to provide a BannerService instance from our custom component that displays the banner:
#Component({
selector: 'app-component-with-its-own-banner',
// template, css, etc
providers: [BannerService]
})
export class ComponentWithItsOwnBanner {
// ...
}
The template of this component also includes the banner component selector:
<baner [alerts]="bannerService.alerts$ | async"></baner>
Everything else can stay exactly the same. You don't need to create any additional behavior subjects.

Ember.js: Converting vanilla array into Ember array not working

I have an array that I'm converting to Ember array with A() as I want to use some of Ember array methods, like filterBy(), but it's not producing the result I want. What is the proper way to convert a vanilla array into an Ember array?
Ember:
import Component from '#ember/component';
import { computed } from '#ember/object';
import { A } from '#ember/array';
export default Component.extend({
movieGenreIds: computed.map('movies', function(movie, index) {
return movie.genre_ids;
}),
genresNames: computed('movieGenreIds', 'allGenres', function() {
let genresArray = A(this.get('genres')); // <--- conversion here
this.get('movieGenreIds').forEach((movieGenreId, movieGenreIndex) => {
console.log('MOVIE_GENRE_IDS!!!', genresArray);
console.log('FILTERBY ID^^^', genresArray.filterBy('id', movieGenreIds.toString())); // <-- not returning desired results
});
}),
});
Ember route (data is from themoviedb api and models represent the data structure in the json provided):
import Route from '#ember/routing/route';
import RSVP from 'rsvp'
export default Route.extend({
model() {
return RSVP.hash({
movies: this.store.findAll('movie'),
genres: this.store.findAll('genre'),
})
.then(data => data);
},
});
Okay, first .then(data => data); makes literally nothing. Just remove this.
Next if you don't disable the prototype extension for Arrays you dont need to convert normal arrays to ember arrays. so replace this:
let genresArray = A(this.get('genres'));
with this:
let genresArray = this.get('genres');
or this in ember 3.1+:
let genresArray = this.genres;
Now an interesting question is, what is genre_ids on the movie model? I strongly assume its a computed property that returns an array. But some code would help.
However your dependency key for movieGenreIds is wrong. You probably should do this:
movieGenreIds: computed.map('movies.#each.genre_ids', function(movie, index) {
return movie.genre_ids;
}),
However your actual problem is now probably that this will probably return an array of arrays. So something like this:
[[1,2],[3],[],[4,5]]
Now you do .forEach on it, however movieGenreId will now probably still be an array. Next you do movieGenreId.toString() (you actually do movieGenreIds.toString(), but I assume this is a typo, because this wouldn't make sense because you don't use movieGenreId inside the loop then). However doing .toString() on an array will probably not give you the desired result - an id.
And so probably your fix is to fix the movieGenreIds CP (the code is for ember 3.1+):
movieGenreIds: computed('movies.#each.genre_ids', function() {
return this.movies
.map(m => m.genre_ids)
.reduce((a, b) => [...a, ...b]);
}),

add data to the end of a behavior object array Angular 5

I have some data that I want to be shared with my entire app so I have created a service like so..
user.service
userDataSource = BehaviorSubject<Array<any>>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
then in my component Im getting some data from an api and then sending that data to userDataSource like so..
constructor(
private: userService: UserService,
private: api: Api
){
}
ngOnInit() {
this.api.getData()
.subscribe((data) => {
this.userService.updateUserData(data);
})
}
now that all works but.. I want to be able to add data to the end of the array inside the userDataSource so basically the equivalent of a .push am I able to just call the updateUserData() function and add more data or will doing that overwrite what is currently in there?
Any help would be appreciated
You can add a new method to your service like addData in which you can combine your previous data with new data like.
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
userDataSource: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
addData(dataObj) {
const currentValue = this.userDataSource.value;
const updatedValue = [...currentValue, dataObj];
this.userDataSource.next(updatedValue);
}
}
For someone that may come accross this issue with a BehaviorSubject<YourObject[]>.
I found in this article a way to properly add the new array of YourObject
import { Observable, BehaviorSubject } from 'rxjs';
import { YourObject} from './location';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ObjService {
private theObjData: BehaviorSubject<YourObject[]> = new BehaviorSubject<YourObject[]>(null);
constructor() {
}
public SetObjData(newValue: YourObject[]): void {
this.theObjData.next(Object.assign([], newValue));
}
}
How to update data:
// inside some component
this.api.userData().subscribe((results:YourObject) =>
this.objService.SetObjData(results);
)
How to observe changes on other component
// inside another component
ngOnInit() {
this.objService.GetAccountStatements().subscribe((results) =>
...
)
}
Normally Observables and Subjects are meant to be streams of data, not an assignment of data. BehaviorSubjects are different because they hold their last emitted value.
Normally Subjects or BehaviorSubjects inside of a contained class (like a Service) do not want to expose themselves publicly to any other classes, so it's best practice to access their properties with getters or methods. This keeps the data stream cold to all subscribers.
However, since the BehaviorSubject holds the last emitted value, there's a few options here. If all subscribers need a concatenated stream of data from every emission, you could access the last emitted value and append to it:
userDataSource = BehaviorSubject<any[]>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.push(data));
}
...or, in what might be considered better practice, Subscribers to this Subject could do their own transformation on the stream:
this.api.userData()
.scan((prev, current) => prev.push(current). [])
.subscribe((data) => {
this.concatenatedUserData = data;
});
Use concat to add object
userDataSource = BehaviorSubject<Array<any>>([]);
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.concat(data));
}
Use filter to remove object
removeUserData(data) {
this.userDataSource.next(this.userDataSource.value.filter(obj => obj !== data));
}

Call component logic when state changes in ngrx

I'm currently developing an application with Angular using redux principle with ngrx.
I'm looking for a best practice for reacting to state changes and call some component logic depending on this state. I'll give you an (simplified) example to make clear what I mean:
reducers.ts
import {createSelector} from 'reselect';
export const getViewTypeOrFilterChanged = createSelector(isLoading, getActiveViewType, getActiveFilter, (isLoading, activeViewType, activeFilter) => {
// ensure that data is loaded
if (!isLoading) {
return {
activeViewType: activeViewType,
activeFilter: activeFilter
};
}
});
example-component.ts
#Component({ ... })
export class ExampleComponent implements OnInit {
// properties ...
constructor(private store: Store<fromRoot.AppState>) {
}
ngOnInit() {
this.subscriptions.push(
this.store.select(fromRoot.getViewTypeOrFilterChanged).subscribe((result) => {
if (result) {
this.property1 = result.activeType;
this.dependentHelperClass.method1(result.activeFilter);
this.method1();
this.method2(result.activeFilter);
this.method3();
}
})
);
}
ngOnDestroy() {
this.subscriptions.forEach((subscription: Subscription) => {
subscription.unsubscribe();
});
}
// methods ...
}
As you can see I'm also using reselct to combine three different slices of state within a selector (getViewTypeOrFilterChanged). In the subscription to this selector I then want to take some actions according to the combined state.
The thing is, I'm feeling like using ngrx store and subscriptions more in a way of publish/subscribe pattern here and it feels not quite correct. Also the subscriptions (I have multiple ones) in ngOnInit and unsubscriptions in ngOnDestroy bother me, but I can't think of a way achieving the same results using e.g. async pipe.
Is there maybe a more elegant way of reacting to (combined) state changes?
Thanks!
With RxJS you should think of everything as a stream - the following code is just as an example, because I don't really know any of your UI-logic so just look at the structure and not at the logic of the code, since it's more like a very wild guess of mine:
#Component({ ... })
export class ExampleComponent implements OnInit {
private destroyed$ = new Subject<boolean>();
// the following streams can be used in the controller
// as well as in the template via | async
// the .share() is just so the | async pipe won't cause unneccessary stream-creations (the result should be the same regardless of the .share(), it's just a minor performance-enhancement when using multiple | async)
isLoading$ = this.store.select(fromRoot.isLoading).share();
activeViewType$ = this.store.select(fromRoot.getActiveViewType).share();
activeFilter$ = this.store.select(fromRoot.getActiveFilter).share();
activeViewTypeAndFilter$ = Observable.combineLatest(this.activeViewType$, this.activeFilter$).share();
constructor(private store: Store<fromRoot.AppState>) {
}
ngOnInit() {
this.isLoading$
.filter(isLoading => !isLoading) // the initial stream will not emit anything until "loading" was false once
.switchMapTo(this.activeViewTypeAndFilter$)
.do([viewType, filter] => {
this.dependentHelperClass.method1(activeFilter);
this.method1();
this.method2(activeFilter);
this.method3();
})
.takeUntil(this.destroyed$) //this stream will automatically be unsubscribed when the destroyed$-subject "triggers"
.subscribe();
}
ngOnDestroy() {
this.destroyed$.next(true);
this.destroyed$.complete();
}
// methods ...
}
As I said: logic-wise I cannot say if this is what you need, but that's just a question of using different operators and/or a different order to arrange your "main-stream" differntly.

How to Access the Component Inside of Annotation in Angular2?

This is just me trying to get an understanding of the lifecycle and try and perform some logic before loading the component so I found the CanActivate Annotation.
I have a function in the Component that I want to call so I need the Component; I have injected it... it seems overly complex.
// HomeComponent Component
#Component({
selector: 'HomeComponent',
template: '<h2>HomeComponent Us</h2>'
})
#CanActivate((next,prev) => {
let injector: any = Injector.resolveAndCreate([HomeComponent]);
let theComponent: HomeComponent = injector.get(HomeComponent);
return theComponent.canActivate(next,prev)
})
class HomeComponent {
data = {}
myresolver: any
constructor() {
console.log("in constructor", this.data)
}
setData(data: any) {
this.data = data
}
canActivate(nextInstr, currInstr) {
console.log("in my resolver");
var that = this;
return new Promise(function(resolve, reject) {
setTimeout(function() {
var data = { data: "Aaron" };
that.setData(data)
resolve(data);
}, 2000)
});
}
}
In fact you can't since processing within the annotation is called right before instantiating (or not) the component.
I guess that you want to use the resolve feature of Angular1 router into Angular2. In fact, such feature isn't supported yet. For more details you can have a look at this issue in the Angular github:
https://github.com/angular/angular/issues/4015
The two following links give you some workarounds for your use case:
Angular 2 - equivalent to router resolve data for new router
Using Resolve In Angular2 Routes
Hope it helps you,
Thierry

Categories

Resources