import { Component, Input, Output, EventEmitter } from '#angular/core';
var colorPickerCss = "app/css/ui/color-picker.css";
var colorPickerTemplate = "app/partials/color-picker.html";
#Component({
selector: 'color-picker',
styleUrls: [colorPickerCss],
templateUrl: colorPickerTemplate
})
export class ColorPicker{
#Input() colors: string[] = [];
#Output() selectedColor = new EventEmitter();
isSelectorVisible : boolean = false;
showSelector(value: boolean){
this.isSelectorVisible = value;
}
selectColor(color: string){
this.showSelector(false);
this.selectedColor.next({color});
}
} ;
I have written the above code, but I want to understand the functioning of it. My question is, what is the .next() function on this line this.selectedColor.next({color}). What library is it from? I have mentioned the imports above, but I can't really get to the actual definition of this function.
An EventEmitter, extends Subject. When you use next, you fire off an event that all subscribers will listen too. See here. emit is the preferred alternative.
Related
I want detach the change detection for all the compontents out of the current viewport
see demo online
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef, ViewChild, OnInit, OnDestroy, AfterViewInit } from '#angular/core';
#Component({
selector: 'hello',
template: `<div #counter>[{{index}}] {{count}}</div>`,
styles: [`div { border: 1px solid red; padding: 5px 5px 5px 5px; }`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HelloComponent implements OnInit, AfterViewInit {
#ViewChild('counter', { static: false }) counter: ElementRef;
#Input() index: number;
public count = 0;
public visible = true;
constructor(private cdr: ChangeDetectorRef){}
ngOnInit() {
setInterval(() => {
this.count++;
this.cdr.markForCheck();
}, 1000);
}
ngAfterViewInit() {
const hideWhenBoxInView = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio <= 0) { // If not in view
this.cdr.detach();
this.visible = false;
} else {
this.visible = true;
this.cdr.reattach();
this.cdr.markForCheck();
}
// console.log(this.index, this.visible);
});
hideWhenBoxInView.observe(this.counter.nativeElement);
}
}
it works, but with over 1000 components the performance is very bad.
Are my attaching/detaching change detection correct?
You are calling setInterval() for every component including those that are not in view. Change detection is not running but you are still calling the function in setInterval() 1000 times per second which explains the lag.
By the way, rendering a scroll list with 1000 items affects performance too. Browsers will render everything and need to calculate various paints when scrolling through the list despite being out of viewport. You should render such long list lazily, see Virtual Scrolling in Angular 7
You are also calling .markForCheck() on components that are out of view, check whether component is visible before calling that.
See StackBlitz
ngOnInit() {
this.subscriptions.add(
interval(1000).subscribe(() => {
this.count++;
if (this.visible) {
this.cdr.markForCheck();
}
})
);
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
ngAfterViewInit() {
const hideWhenBoxInView = new IntersectionObserver(entries => {
if (entries[0].intersectionRatio <= 0) {
// If not in view
this.cdr.detach();
this.visible = false;
} else {
this.visible = true;
this.cdr.reattach();
this.cdr.markForCheck();
}
});
hideWhenBoxInView.observe(this.counter.nativeElement);
}
Maybe using trackBywill allow to avoid check whether it is in viewport.
<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
trackBy gives you a possibility to choose what property/condition angular should check changes against.
trackByFn(index, item) {
return item.someUniqueIdentifier;
// return index(if id is not unique) or unique id;
}
As Angular docs says:
A function that defines how to track changes for items in the
iterable.
When items are added, moved, or removed in the iterable, the directive
must re-render the appropriate DOM nodes. To minimize churn in the
DOM, only nodes that have changed are re-rendered.
By default, the change detector assumes that the object instance
identifies the node in the iterable. When this function is supplied,
the directive uses the result of calling this function to identify the
item node, rather than the identity of the object itself.
The function receives two inputs, the iteration index and the node
object ID.
I have two siblings in a parent, and data for a sibling is being updated, data is updated, but
ngOnInit(){}
not called.
https://stackblitz.com/edit/angular-5ctf2z?file=src/app/child2/child2.component.ts
Please help me,
Thanks in advance
Replace ngOnInit with ngOnChange as follows:
export class Child2Component implements OnChange {
#Input() data:any;
#Output() onEmit:EventEmitter<any> = new EventEmitter();
constructor() { }
ngOnChange() {
if(this.data.test1%2){
this.data.test2 += this.data.test1
}
}
}
It seems there is no way to watch changes in the parent component when using two-way data binding.
I have a custom input component for collecting a tag list. Two-way data binding is setup and working between this component and its parent.
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
In the parent component how do you watch the bound variable for changes? While it's always up to date (I've confirmed this) I cannot find any guidance on reacting to changes.
I've tried:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
And
skillTagUpdate(event){
console.log(event); // nothing
}
UPDATE:
TWDB IMHO is not what it is advertised to be. Whenever I arrive at this place where TWDB seems to be a solution I rearchitect for a service and or observable communication instead.
When you implement a two way binding of your own, you have to implement an event Emitter. The syntax for that is mandatory.
this means that you have a hook to listen to if the value changes.
Here is a demo :
<hello [(name)]="name" (nameChange)="doSomething()"></hello>
_name: string;
#Output() nameChange = new EventEmitter();
set name(val) {
this._name = val;
this.nameChange.emit(this._name);
}
#Input()
get name() {
return this._name;
}
counter = 0;
ngOnInit() {
setInterval(() => {
this.name = this.name + ', ' + this.counter++;
}, 1000);
}
Stackblitz
From what I know, this seems the less annoying way to use it, and any two way binding will follow the same rule no matter what, i.e. it ends with the Change word !
Your implementation is actually not two-way databinding, the parent and child component are just sharing a reference on the same skillTags variable.
The syntax [(tags)]='skillTags' is syntaxic sugar for [tags]='skillTags' (tagsChange)='skillTags = $event'
You need to implement tagsChange in the child component like this: #Output('tagsChange') tagsChange = new EventEmitter<any>();, then any time you want to modify tags into the children component, dont do it directly, but use this.tagsChange.emit(newValue) instead.
At this point, you'll have real two-way databinding and the parent component is the unique owner of the variable (responsible for applying changes on it and broadcasting changes to the children).
Now in your parent component, if you want to do more than skillTags = $event (implicitly done with [(tags)]='skillTags'), then just add another listener with (tagsChange)='someFunction($event)'.
StackBlitz Demo
Don't know if this is what you're looking for, but have you tried using #Input()?
In child component
#Input() set variableName(value: valueType) {
console.log(value);
}
In parent component
<input-tags formControlName="skillField" [(tags)]='skillTags'
[variableName]="skillTagUpdate($event)"></input-tags>
The input function is called every time the object binded to the function is changed.
you could listen to the change:
<input-tags formControlName="skillField" [tags]='skillTags' (tagsChange)='skillTags=$event; skillTagUpdate();'></input-tags>
or use getter and setter:
get skillTags(): string {
return ...
}
set skillTags(value) {
variable = value;
}
another approach:
export class Test implements DoCheck {
differ: KeyValueDiffer<string, any>;
public skillTags: string[] = [];
ngDoCheck() {
const change = this.differ.diff(this.skillTags);
if (change) {
change.forEachChangedItem(item => {
doSomething();
});
}
}
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
}}
1.you can use output(eventemitter)
2.easiest solution is rxjs/subject. it can be observer and observable in same time
Usage:
1.Create Subject Property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
2.When event happend in child page/component use :
logout(){
this.authService.loginAccures.next(false);
}
3.And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
Update
for two-way binding you can use viewchild to access to your child component items and properties
<input-tags #test></<input-tags>
and in ts file
#ViewChild('test') inputTagsComponent : InputTagsComponent;
save()
{
var childModel = this.inputTagsComponent.Model;
}
I recall reading the excerpt below from a blog.
$timeout adds a new event to the browser event queue (the rendering engine is already in this queue) so it will complete the execution before the new timeout event.
I'm wondering if there is a better way in angular/ javascript than using
setTimeout(() => {
// do something after dom finishes rendering
}, 0);
to execute code when the DOM has completely finished a task such as updating an *ngFor and rendering the results on the page.
You might try the ngAfterViewInit life-cycle hook, which is the chronologically last single-fire life-cycle hook.
https://angular.io/guide/lifecycle-hooks
It works much like ngInit but it fires after the view and child views have initialized.
If you need something that fires every time the DOM finishes you can try ngAfterViewChecked or ngAfterContentChecked.
problem:
I need to run a function sometimes after some parts loaded. (I wanted to stretch out an input and a label)
ngAfterViewInit and route change detection didn't solve my problem
Solution:
I made a component which
import { Component, AfterViewInit } from '#angular/core';
declare var jquery: any;
declare var $: any;
#Component({
selector: 'app-inline-label',
templateUrl: './inline-label.component.html',
styleUrls: ['./inline-label.component.scss']
})
/** InlineLabel component*/
/**
this component stretch inline labels and its input size
*/
export class InlineLabelComponent implements AfterViewInit {
/** InlineLabel ctor */
constructor() {
}
ngAfterViewInit(): void {
var lblWidth = $('.label-inline').width();
var parentWidth = $('.label-inline').parent().width();
var fieldWidth = parentWidth - lblWidth;
$('.form-control-inline').css("width", fieldWidth);
}
}
then I used it anywhere in my html like
<app-inline-label></app-inline-label>
even if my html had *ngIf="", I used app-inline-label inside that tag and solved all my problems
Actually it will be fired exactly when <app-inline-label> </app-inline-label> being rendered
If the function to be rendered multiple times ngAfterContentChecked will be preferable.
app.component.ts
export class AppComponent implements OnInit, OnDestroy {
searchRegister: any = [];
constructor() {
}
ngAfterContentChecked(): void {
this.setHTMLElements();
}
setHTMLElements() {
this.searchRegister = ['cards-descriptor__subtitle','insights-card__title','article-headline__title','wysiwyg__content','footer-logo__heading','hero-breadcrumbs__blurb','multi-column-text__body','small-two-column-image-text__blurb','two-column-image-text__blurb','image-blurbs-expandable__desc',];
for (var val of this.searchRegister) {
var classLength = this.dom.body.getElementsByClassName(val).length;
for (var i = 0; i <= classLength; i++) {
if (
this.dom.body.getElementsByClassName(val)[i]?.innerHTML != undefined
) {
this.dom.body.getElementsByClassName(val)[
i
].innerHTML = this.dom.body
.getElementsByClassName(val)
[i]?.innerHTML?.replace(/[®]/gi, '<sup>®</sup>');
}
}
}
}
}
Other.component.ts
import { AppComponent } from '../../app.component';
export class IndustryComponent implements OnInit {
constructor(private appComponent: AppComponent) { }
ngAfterContentChecked(): void {
this.appComponent.setHTMLElements();
}
}
i'm using angular2 and i'm trying to get the .style.width attribute from an element which is loaded inside a *ngFor- loop. I want to use it to define the width of some other elements. This should be done while the page loads. I dont want to store the width as some var inside my code. I want to get it directly from the dom.
promise :
let kistenPromise = new Promise(function(resolve, reject){
let itemListContainer = document.getElementById("itemContainer");
if(itemListContainer.children[0] != undefined){
resolve(itemListContainer.children);
}else{
reject(Error("Promise was not fullfilled!"));
}
}.bind(this));
handler:
kistenPromise.then(
function(result){
console.log(result);
}.bind(this), function(err){
console.log(err);
}.bind(this));
html:
<div class="itemFrame" id="itemContainer">
<div class="listStyle" *ngFor="let item of list">{{item}}</div>
</div>
When i use the colde like this it only returns the Promise was not fullfilled.
However if i try itemList.children != undefined and return the .length it will return 0.What am i missing?
Thanks in advance!
You may want to use AfterViewInit. Add a local variable #itemContainer to your container:
<div class="itemFrame" id="itemContainer" #itemContainer>
<div class="listStyle" *ngFor="let item of list">{{item}}</div>
</div>
Then in your component you can check the element for children (or pass the element to another function that checks for it):
import { ViewChild, ElementRef, AfterViewInit } from '#angular/core';
export class YourComponent implements AfterViewInit {
#ViewChild('itemContainer') itemContainer: ElementRef;
ngAfterViewInit() {
if (this.itemContainer.nativeElement.children.length) {
// ...
} else {
// ...
}
}
}
getElementById returns a single element, because id should be unique, and not an array. Other queries do return and array (for example getElementsByClassName or getElementsByTagName).
Where is your handler written? It may be getting executed before the view is even initialized. If so, try moving it to ngAfterViewInit
your.component.ts
import { AfterViewInit } from '#angular/core'
export class YourComponent implements AfterViewInit {
ngAfterViewInit() {
kistenPromise.then(...)
}
}