Angular2 Observables best practice - javascript

let's say I have a component which has a child component. For example this simple calendar:
Here is its template:
<month-board [current-month]="currentMonth$"></month-board>
<button (click)="decrement()">Prev</button>
<button (click)="increment()">Next</button>
when the buttons are clicked the month-board which subscribed to currentMonth$ is subscribed is changing the displayed month.
currentMonth$ type is Observable<Moment>.
My question is: is it a good practice to pass Observables to child components in Angular2? is there any better way to do this?
Parent full code:
#Component({
selector: 'month-view',
templateUrl: 'app/components/month-view/month-view.html',
styleUrls: ['app/components/month-view/month-view.css'],
directives: [MonthBoard],
})
export class MonthView {
currentMonth: Moment = moment();
currentMonth$: Observable<Moment>;
currentMonthObserver: Observer<Moment>;
decrement: Function;
increment: Function;
constructor() {
this.currentMonth$ = Observable.create((observer: Observer<Moment>) => {
this.currentMonthObserver = observer;
});
const decrementCounter$: Observable<Function> = Observable.create((observer) => {
this.decrement = () => {
observer.next();
};
});
const incrementCounter$: Observable<Function> = Observable.create((observer) => {
this.increment = () => {
observer.next();
};
});
this.currentMonth$.subscribe();
Observable
.merge(
decrementCounter$.map(() => - 1),
incrementCounter$.map(() => + 1)
)
.startWith(0)
.scan((currentCount: number, value: number) => currentCount + value)
.subscribe((count: number) => {
this.currentMonthObserver.next(this.currentMonth.clone().add(count, 'M'));
});
}
}
Child full code:
#Component({
selector: 'month-board',
templateUrl: 'app/components/month-board/month-board.html',
styleUrls: ['app/components/month-board/month-board.css'],
directives: [DayCell]
})
export class MonthBoard implements OnInit{
#Input('current-month') currentMonth: Observable<Moment>;
weeks: Moment[][];
constructor(private calendarHelper: CalendarHelper) {
this.weeks = this.calendarHelper.getMonthWeeks();
}
ngOnInit() {
this.currentMonth.subscribe((month: Moment) => {
this.weeks = this.calendarHelper.getMonthWeeks(month);
});
}
}

You can do it that way, the other way is with #input. It's very easy to pass values from parent to child with it.
https://angular.io/docs/ts/latest/api/core/Input-var.html
I don't think it's necessarily bad to pass observables that way to your child component. For example I have a service that uses an observable that my whole application uses to watch for logged in events. But for a Calendar you might find yourself wanting to pass different values in different places on the same observable. If you do that you can always make another observable. Or manipulate it in different ways for each component.
But for readability I would definitely just use #input, that way I only have to go to the parent component to figure out what I am passing around.

Related

function variable dynamic setting

This question related to Syntactically anonymous/Arrow Function/add-hoc/factory DP functions:
I have a component which is embedded in the Html.
The component has a click event which is binded to a function. This function content depend on another component which has a reference to this component.
This is the component with the click event:
HTML:
<div (click)="doSomething()">Content.....</div> \\ Should it be with a brackets ?
In the component I just want to define the function signature:
#Component({
selector: 'app-embedded'
})
export class className
{
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) => any; //The function get a boolean parameter as input and return void or any
}
Now this is where the component is embedded:
<div >
<app-embedded #emb></app-embedded>
</div>
This is the component of the container of the embedded component, which has a reference to the embedded component:
#Component({
selector: 'app-container',
})
export class container
{
#ViewChild('emb') private emb: ElementRef;
booleanParam : booelan;
constructor()
{
emb.doSomething = containerFunction(true);
}
containerFunction(booleanParam : boolean)
{
// do something in this context
}
}
The idea is that this embedded component is embedded in many other containers and whenever the click event triggered a function that was set in the doSomething function variable should be executed.
What changes in the code I need to do in order to accomplish this ?
The best way i see of doing this would be to simply use an event emitter and capture the event on the other side? so embedded would have this:
#Component({
selector: 'app-embedded'
})
export class className
{
#Output()
public something: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) {
this.something.emit(booleanparams);
}; //The function get a boolean parameter as input and return void or any
}
Then where it is called:
<div >
<app-embedded #emb (something)="doSomething($event)"></app-embedded>
</div>
Other solution that would allow a return
#Component({
selector: 'app-embedded'
})
export class className
{
#Input()
public somethingFunc: (boolean)=>any;
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) {
let w_potato = this.somethingFunc(booleanparams);
//Do whatever you want with w_potato
}; //The function get a boolean parameter as input and return void or any
}
in this case the view would be
<div >
<app-embedded #emb [somethingFunc]="doSomething"></app-embedded>
</div>
I hope this helps! Passing the function or emitting an event will be much more angular than trying to modify an instance of a component. On top of that, a constructor is only called once when Angular starts up so #emb at that time will not be defined to be anything. If you wanted to do it that way you would have to bind yourself in something ngAfterViewInit.
But again, I think that passing it through attributes will be much more angular looking.
Good Luck let me know if this doesn't suit your answer.

How to debounce a Keyboard Input (keyUp) in Angular and intoduce Subjects/ Observables [duplicate]

I am trying to call to a service on input key-up event.
The HTML
<input placeholder="enter name" (keyup)='onKeyUp($event)'>
Below is the onKeyUp() function
onKeyUp(event) {
let observable = Observable.fromEvent(event.target, 'keyup')
.map(value => event.target.value)
.debounceTime(1000)
.distinctUntilChanged()
.flatMap((search) => {
// call the service
});
observable.subscribe((data) => {
// data
});
}
It was found from the network tab of the browser that, it is calling the key-up function on every key-up event(as it is supposed to do), but what I am trying to achieve is a debounce time of 1sec between each service call. Also, the event is triggered if I move the arrow key move.
plunkr link
So the chain is really correct but the problem is that you're creating an Observable and subscribe to it on every keyup event. That's why it prints the same value multiple times. There're simply multiple subscriptions which is not what you want to do.
There're obviously more ways to do it correctly, for example:
#Component({
selector: 'my-app',
template: `
<div>
<input type="text" (keyup)='keyUp.next($event)'>
</div>
`,
})
export class App implements OnDestroy {
public keyUp = new Subject<KeyboardEvent>();
private subscription: Subscription;
constructor() {
this.subscription = this.keyUp.pipe(
map(event => event.target.value),
debounceTime(1000),
distinctUntilChanged(),
mergeMap(search => of(search).pipe(
delay(500),
)),
).subscribe(console.log);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
See your updated demo: http://plnkr.co/edit/mAMlgycTcvrYf7509DOP
Jan 2019: Updated for RxJS 6
#marlin has given a great solution and it works fine in angular 2.x but with angular 6 they have started to use rxjs 6.0 version and that has some slight different syntax so here is the updated solution.
import {Component} from '#angular/core';
import {Observable, of, Subject} from 'rxjs';
import {debounceTime, delay, distinctUntilChanged, flatMap, map, tap} from 'rxjs/operators';
#Component({
selector: 'my-app',
template: `
<div>
<input type="text" (keyup)='keyUp.next($event)'>
</div>
`,
})
export class AppComponent {
name: string;
public keyUp = new Subject<string>();
constructor() {
const subscription = this.keyUp.pipe(
map(event => event.target.value),
debounceTime(1000),
distinctUntilChanged(),
flatMap(search => of(search).pipe(delay(500)))
).subscribe(console.log);
}
}
Well, here's a basic debouncer.
ngOnInit ( ) {
let inputBox = this.myInput.nativeElement;
let displayDiv = this.myDisplay.nativeElement;
let source = Rx.Observable.fromEvent ( inputBox, 'keyup' )
.map ( ( x ) => { return x.currentTarget.value; } )
.debounce ( ( x ) => { return Rx.Observable.timer ( 1000 ); } );
source.subscribe (
( x ) => { displayDiv.innerText = x; },
( err ) => { console.error ( 'Error: %s', err ) },
() => {} );
}
}
If you set up the two indicated view children (the input and the display), you'll see the debounce work. Not sure if this doesn't do anything your does, but this basic form is (as far as I know) the straightforward way to debounce, I use this starting point quite a bit, the set of the inner text is just a sample, it could make a service call or whatever else you need it to do.
I would suggest that you check the Angular2 Tutorial that show a clean and explained example of something similar to what you're asking.
https://angular.io/docs/ts/latest/tutorial/toh-pt6.html#!#observables

Dynamic Component in Angular 2

I am trying to implement the tab view component of Prime NG. but my tabs are dynamic in nature ie.
So when the container is loaded it sends multiple AJAX requests for data inside the component.(Maybe the component is initialized multiple times?)
Another thing, in one of the components, moving mouse gives Thousands of errors on the console.
ERROR Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed
ERROR CONTEXT [object Object]
Not sure why. Used the same component in another place and there was no issue.
Even if I remove the dynamic nature of the components and just place 4 static tabs, everything works perfectly.(Right now the same 4 components are coming from server).
Html Template:
<div class="col-md-12 padding0">
<div class="tabViewWrapper">
<p-tabView (onChange)="handleChange($event)">
<p-tabPanel header="{{tab.tabName}}" *ngFor="let tab of tabs" >
<dynamic-component [componentData]="componentData"></dynamic-component>
</p-tabPanel>
</p-tabView>
<div>
</div>
Component:
#Component({
selector: 'tab-view',
templateUrl: './tab-view.component.html',
styleUrls: ['./tab-view.component.scss'],
encapsulation: ViewEncapsulation.None,
entryComponents: [GenericDataTableComponent, SingleEditCategoryExplorerComponent, AssetsDataTableComponent]
})
export class TabViewComponent implements OnInit {
private ngUnsubscribe: Subject<void> = new Subject<void>();
private componentData = null;
private tabs: Array<any>;
private index:number;
private disabledTabs:Array<any>;
private disabledTabsWhenMetaDataClicked:Array<any>;
versionConfig = {
url: AppSettingProperties.DATA_TABLE_VALUES.VERSIONS_URL,
dateLocale: AppSettingProperties.DATA_TABLE_VALUES.LOCALE,
header: AppSettingProperties.DATA_TABLE_VALUES.VERSIONS_HEADER
};
relatedConfig = {
url: AppSettingProperties.BASEURL + AppSettingProperties.DATA_TABLE_VALUES.RELATED_ENDPOINT,
header: AppSettingProperties.DATA_TABLE_VALUES.RELATED_HEADER
};
constructor(private assetDataLoadedService: AssetDataLoadedService, private assetDetailsService: AssetDetailsService, private assetDetailDataModel:AssetDetailDataModel) { }
#ViewChildren(DynamicContainerComponent) dynamicContainers: QueryList<DynamicContainerComponent>;
ngOnInit() {
this.disabledTabs = [];
//Set items to be disabled when Metadata button is clicked
this.disabledTabsWhenMetaDataClicked = [AppSettingProperties.TAB_RELATEDITEMS, AppSettingProperties.TAB_VERSIONS];
//Disable the tabs as per the condistions
this.disableTabsAsPerRequirement();
//Assigning tabs
this.tabs = this.assetDetailsService.systemTabs;
}
getInitialSelected(tab){
return this.selectedTab == this.tabs.indexOf(tab);
}
get selectedTab():number{
return this.index;
}
set selectedTab(val:number){
this.index = val;
var defaultTab = this.tabs[this.index]['tabName'];
if(!this.assetDetailDataModel.catalogId){
this.assetDataLoadedService.assetDetailPublisher.subscribe(data=>{
this.loadComponentByTab(defaultTab);
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
});
}
else{
this.loadComponentByTab(defaultTab);
}
}
handleChange(e) {
let tabName: string = e.originalEvent.currentTarget.innerText;
this.selectedTab = e.index;
//this.loadComponentByTab(tabName);
}
loadComponentByTab(tabName:string){
switch (tabName) {
case AppSettingProperties.TAB_METADATA:
this.componentData = { component: AssetsDataTableComponent, inputs: {} }
break;
case AppSettingProperties.TAB_CATEGORY:
let categoryConfig: object = {"catalog_id":this.assetDetailDataModel.catalogId,"item_id":this.assetDetailDataModel.assetId};
console.log(categoryConfig);
this.componentData = { component: SingleEditCategoryExplorerComponent, inputs: { tabConfig: categoryConfig } }
break;
case AppSettingProperties.TAB_RELATEDITEMS:
this.componentData = { component: GenericDataTableComponent, inputs: { tabConfig: this.relatedConfig } }
break;
case AppSettingProperties.TAB_VERSIONS:
this.componentData = { component: GenericDataTableComponent, inputs: { tabConfig: this.versionConfig } }
break;
}
}
}
Dynamic Component:
import { Component, Input, ViewContainerRef, ViewChild, ReflectiveInjector, ComponentFactoryResolver } from '#angular/core';
#Component({
selector: 'dynamic-component',
template: `<div #dynamicComponentContainer></div>`,
})
export class DynamicComponent {
private currentComponent = null;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) { }
// component: Class for the component you want to create
// inputs: An object with key/value pairs mapped to input name/input value
#Input() set componentData(data: { component: any, inputs: any }) {
console.log("Building Component Start");
if (!data) {
return;
}
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(data.inputs).map((inputName) => { return { provide: inputName, useValue: data.inputs[inputName] }; });
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(data.component);
// We create the component using the factory and the injector
let component = factory.create(injector);
// We insert the component into the dom container
this.dynamicComponentContainer.insert(component.hostView);
// We can destroy the old component is we like by calling destroy
if (this.currentComponent) {
this.currentComponent.destroy();
}
this.currentComponent = component;
console.log("Building Component Finish");
}
}
Another thing is that the console start in dynamic component is shown 8 times.
While console finish is shown 4-5 times.
Seems really weird behavior.
As #echonax wrote in comment.
This is because you are trying to iterate something that is not an array.
Most probably this.tabs.
You can try and write out {{tabs|json}} in a div instead of the *ngFor
Since your response takes sometime to load your DOM will have tabs variable as undefined array.
To solve this initialize the variable to an empty array as below
tabs:Array<any> = []
or inside the constructor as
constructor(){
this.tabs = [];
}

Angular 2 Filter Pipe

Trying to write a custom pipe to hide some items.
import { Pipe } from '#angular/core';
// Tell Angular2 we're creating a Pipe with TypeScript decorators
#Pipe({
name: 'showfilter'
})
export class ShowPipe {
transform(value) {
return value.filter(item => {
return item.visible == true;
});
}
}
HTML
<flights *ngFor="let item of items | showfilter">
</flights>
COMPONENT
import { ShowPipe } from '../pipes/show.pipe';
#Component({
selector: 'results',
templateUrl: 'app/templates/results.html',
pipes: [PaginatePipe, ShowPipe]
})
My item has the property of visible, which can be true or false.
However nothing showing, is there something wrong with my pipe?
I think my pipe is working because when I change the pipe code to:
import { Pipe } from '#angular/core';
// Tell Angular2 we're creating a Pipe with TypeScript decorators
#Pipe({
name: 'showfilter'
})
export class ShowPipe {
transform(value) {
return value;
}
}
It will show all items.
Thanks
I'm pretty sure this is because you have an initial value of [] for items. When you then later add items to items, the pipe is not reexecuted.
Adding pure: false should fix it:
#Pipe({
name: 'showfilter',
pure: false
})
export class ShowPipe {
transform(value) {
return value.filter(item => {
return item.visible == true;
});
}
}
pure: false has a big performance impact. Such a pipe is called every time change detection runs, which is quite often.
A way to make a pure pipe being called is to actually change the input value.
If you do
this.items = this.items.slice(); // create a copy of the array
every time after items was modified (added/removed) makes Angular recognize the change and re-execute the pipe.
It is not recommended to use impure pipe. I will impact your performance.
It will be called even source has not been changed.
So the correct alternative to be is change the reference of your returning object.
#Pipe({
name: 'showfilter'
})
export class ShowPipe {
transform(value) {
value = value.filter(item => {
return item.visible == true;
});
const anotherObject = Object.assign({}, value) // or else can do cloning.
return anotherObject
}
}

how to create a component that i can set fetching url data in an attribute in angular 2

I'm trying to create a application with angular 2 , i want to create a component in angular 2 that I set URL in attribute and want use several times from this component and each component have own data...
i want something like this :
its possible or not?
I'll really appreciate if someone help me.
new movies :
<comp url="www.aaaa.com/movies?type=new"></comp>
old movies :
<comp url="www.aaaa.com/movies?type=old"></comp>
#Component({
selector: 'comp',
template: '<div>{{data}}</div>'
})
export class Component {
#Input() url: string;
constructor(private http:Http) {
}
ngOnChanges(changes) {
this.http.get(this.url)
.map(res => res.json())
.subscribe(val => this.data = val);
}
}
If the component has more than one input then you need to check which one was updated. See https://angular.io/api/core/OnChanges for more details.
You can use compoenent as mentioned above answer.
But for this kind of task I always choose directive. You can also create a directive which will take one parameter and do the stuff.
In this way you don't have to create a tag but in any tag you can apply your directive.
#Directive({
selector: '[comp]',
})
export class compDorective implements OnInit {
#Input() url: string;
constructor(private http:Http, private elementRef: ElementRef) {
}
ngOnInit(changes) {
this.http.get(this.url)
.map(res => res.json())
.subscribe(val => this.elementRef.nativeElement.innerHtml = val);
}
}
you can apply this directive to any element like this
<div comp [url]="www.aaaa.com/movies?type=new"></div>
<span comp [url]="www.aaaa.com/movies?type=old"></span>

Categories