Angular 2 two-way component binding doesn't call parent ngOnChange - data-binding

I have created a plunker here:
http://plnkr.co/edit/8bwqkYQ6tqrpGwHT588y?p=preview
that shows the issue.
Basically, I have 2 components. The first component has a 2-way binding of a property to the child component.
My parent component is:
import { Component, Input, Output, EventEmitter } from '#angular/core'
import { ChildComponent } from "./childComponent"
#Component({
selector: 'parentComponent',
template: `
<div>
Reset<br>
<div>Parent SelectedId: {{selectedId}}</div>
<childComponent [(selectedId)]="selectedId"></childComponent>
</div>
`,
directives: [ChildComponent]
})
export class ParentComponent {
#Input() selectedId: number;
ngOnChanges(changes) {
console.log("Parent changes called!");
}
}
and my child component:
import { Component, Input, Output, EventEmitter } from '#angular/core'
#Component({
selector: 'childComponent',
template: `
<div>
<div>Child SelectedId: {{selectedId}}</div>
</div>
`,
directives: []
})
export class ChildComponent {
#Input() selectedId: number;
#Output() selectedIdChange: EventEmitter<number> = new EventEmitter<number>();
constructor() {
setTimeout(() => {
this.selectedId = 100;
this.selectedIdChange.emit(this.selectedId);
}, 2000);
}
ngOnChanges(changes) {
console.log("Child changes called!");
}
}
In the child, I set a timeout to change the value of selectedId programmatically after 2 seconds, then emit the value back to the parent.
This all works great, except for one thing... the ngOnChange of the parent is only being called once.
I would think that the parent would very much like to know if the child has changed the value, or else what is the point of 2 way binding??
What am I missing here?

The ngOnChange of the parent will only be called if App's selectedId changes, since that's what ParentComponent's input property is bound to.
If you want the parent to be notified of changes made in the child, bind to the xChange event (where x is the name of the input property) – i.e., break up the property and event bindings:
<childComponent [selectedId]="selectedId" (selectedIdChange)="changed($event)"></childComponent>
changed(newValue) {
console.log('newValue', newValue);
this.selectedId = newValue;
}
Plunker

Related

How to use single ag grid in different components dynamically with changing style

I have one angular grid ag-grid which is in component1 which we are using some other component2,
i want to use the same component1 in component3 but while using it in component3 i want to change the class of ag-grid as 'ag-grid-them-dark'. how to pass the configure class to component.
component1 code
<ag-grid-angular class="ag-theme-balham" [gridOptions]="gridOptions"
[rowData]="gridSource" [columnDefs]="columnDefs" [rowClassRules]="rowClassRules"
(gridReady)="onGridReady($event)">
</ag-grid-angular>
this.gridOptions = {
rowData: this.gridSource
};
component3 code
on click of button icon pop up will open which will show aggrid but we want that in ag-grid-dark class
private open1: MatDialogRef<component3>;
dataValues() {
this.dialogRef = this.open1.open(component3, {
width: '100%',
height: '100%',
data: {
context: this, dataTable: this.comingdata, Col: this.column
}
});
}
}
You can create a wrapper component that will wrap you grid with your default parameters or passing them as inputs.
import { Component } from '#angular/core';
#Component({
selector: 'wrapper-component',
templateUrl: './wrapper.component.html',
styleUrls: [ './wrapper.component.scss' ]
})
export class WrapperComponent {
#Input() columnDefs: Array<any>;
#Input() rowData: Array<any>;
}
And you HTML template
<ag-grid-angular
class="ag-theme-alpine"
[rowData]="rowData"
[columnDefs]="columnDefs">
</ag-grid-angular>
You can also make it generic and use it anywhere you want in your app by passing the inputs that will change from view to another which is the preferred way.

How to manipulate a <DIV> style in Angular 8

Want to manipulate style's display. Here is the template:
<div style="display: none" #myDiv />
Thought there are 2 ways to do it:
directly
if (1===1) this.myDiv.style.display = "block";
via #ViewChild
#ViewChild('myDiv', { static: false}) myDiv
if (1===1) this.myDiv.style.display = "block";
none working.
You can use ElementRef for this as follows.
HTML
<div class="my-div" style="display: none" />
TS
export class MyComponent implements AfterViewInit {
myDiv;
constructor(private elementRef:ElementRef) {}
ngAfterViewInit() {
this.myDiv = this.elementRef.nativeElement.querySelector('.my-div');
}
}
Then you can change styles using myDiv variable as follows.
this.myDiv.style.display = 'block';
StackBlitz Demo.
Use ngStyle:
<div [ngStyle]="{'display': flag ? 'block' : 'none'}">
...
</div>
where flag can correspond to any boolean variable based on your logic in the corresponding .ts file.
You can use Renderer2 to set style as well, The prototype of setStyle is as following:
setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void
Parameters:
el: any, The element for whcih you set the style.
style: string, The name of the style.
value: any, The new value for style.
flags RendererStyleFlags2, Flags for style variations. No flags are set by default.
So you have to avoid use of ElementRef because direct access to the dom is not good for security, it is not safe, You can instead use Renderer2 to set Style
Demo example:
https://stackblitz.com/edit/renderer2-example-2-oryw2m?file=src/app/app.component.ts
Code Example:
import { Component, Renderer2, AfterViewInit, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {
#ViewChild('test') test: ElementRef;
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
this.renderer.setStyle(this.test.nativeElement, 'backgroundColor', 'red');
this.renderer.setStyle(this.test.nativeElement, 'color', 'white');
}
}

Angular Dynamic Components - Add Class and other attributes

I am using the following code for creating the dynamic components
import {
Component, OnInit, ViewContainerRef, ViewChild, ViewChildren,
ReflectiveInjector, ComponentFactoryResolver, ViewEncapsulation, QueryList, Input, AfterViewInit
} from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { forEach } from '#angular/router/src/utils/collection';
import { IComponent } from 'app/app.icomponent';
#Component({
encapsulation: ViewEncapsulation.None,
selector: 'dynamic-component',
entryComponents: [HomeComponent, HighlevelSignalComponent],
template: `
<div #dynamicDiv [ngClass]="classFromMenu" >
<ng-template #dynamicComponentContainer></ng-template>
</div>
`,
styleUrls: [
'./dynamic-content.component.css'
],
})
export class DynamicComponent implements IComponent, OnInit, AfterViewInit {
classFromMenu: any;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver, private route: Router,
private activatedRoute: ActivatedRoute, ) {
}
.......
buildComponent(passedData) {
// orderAndObjs has the data for creating the component
this.orderAndObjs.forEach(obj => {
var componentFactory = this.resolver.resolveComponentFactory(obj.component);
var compRef = this.dynamicComponentContainer.createComponent(componentFactory);
// compRef is the component that is created.
//Assuming the component that i am trying to create is <dynamic-component>.
//I want to add either a class or any other attribute like this
//<dynamic-component class="flex">
});
}
}
}
The dynamic-component is created perfectly fine and everything is working as expected. But the only issue is I want to add a class for dynamic-component so that it can be
<dynamic-component class="dynamicClass">
Any help is appreciated :(
Hmm.. I usually add it to the selector of component that is supposed to be an entryComponent ...
selector: 'dynamic-component.someclass',
^^^^^^^^^^^
to add attribute use attribute selector:
selector: 'dynamic-component[myattr=value]',
I call it hidden feature of entryComponents
but its declarative approach and can't be changed at runtime(indeed we can change it)
In Angular 5/6, using Renderer2 from #angular/core, you can do something like below:
constructor(private resolver: ComponentFactoryResolver, private route: Router,
private activatedRoute: ActivatedRoute, private renderer2: Renderer2) {
}
buildComponent(passedData) {
this.orderAndObjs.forEach(obj => {
var componentFactory = this.resolver.resolveComponentFactory(obj.component);
var compRef = this.dynamicComponentContainer.createComponent(componentFactory);
this.renderer2.addClass(compRef.location.nativeElement, 'flex');
});
}
High-level DOM operations are performed with Renderer2 provider. Considering that it was injected, it is:
this.renderer2.addClass(compRef.location.nativeElement, 'dynamicClass');
It should be noticed that depending on how dynamic element is attached to DOM, this may be unnecessary complication.
Considering that dynamicComponentContainer is real DOM element and not <ng-template>, the view of dynamic component can be directly mounted to the container, thus eliminating <dynamic-component> wrapper element:
Given the container:
<div class="dynamicClass" #dynamicComponentContainer></div>
It will be:
var compRef = componentFactory.create(
this.injector,
[],
this.dynamicComponentContainer.element.nativeElement
);

How to pass host component's CSS class to children?

I cannot understand how to pass host component CSS class to a children element. I created a custom element:
...
const CUSTOM_INPUT_VALUE_PROVIDER: Provider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FormFieldComponent),
multi: true,
}
#Component({
moduleId: module.id,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [CUSTOM_INPUT_VALUE_PROVIDER],
selector: 'form-field',
template: `
<div>
<input
(change)="onChange($event.target.value)"
(blur)="onTouched()"
[disabled]="innerIsDisabled"
type="text"
[value]="innerValue" />
</div>
`
})
export class FormFieldComponent implements ControlValueAccessor {
#Input() innerValue: string;
innerIsDisabled: boolean = false;
onChange = (_) => {};
onTouched = () => {};
writeValue(value: any) {
if (value !== this.innerValue) {
this.value = value;
}
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
setDisabledState(isDisabled: boolean) {
this.innerIsDisabled = isDisabled;
}
get value(): any {
return this.innerValue;
}
set value(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
this.onChange(value);
}
}
}
And then use it like this in some reactive form:
<form-field formControlName="title"></form-field>
Problem: I added some validation in FormBuilder to title form control and when it not pass validation, Angular add classic css classes to form-field element: ng-pristine ng-invalid ng-touched.
How i can pass this CSS classes from host element to my input element in form-field component?
It is not duplicate of Angular 2 styling not applying to Child Component. Changing Encapsulation does not resolve the problem.
I think there's a way to do what you want by just knowing the angular classes of the hosting elements and not necessarily passing them down.
If so, your work-around would look something like this in the css of the custom form element:
:host.(ng-class)>>>HTMLelement {
property: value
}
Example:
:host.ng-valid>>>div {
border-left: 5px solid #42a948;
}
The ":host" part of this allows us to use the hosting (parent) html elements
The ">>>" is the deep selector that allows us to apply these styles to all children matching selection property (in this case, we're looking for div elements)

Angular 2 Inheritance components

I have a basewindow.component which will be the base component for all my components. This basewindow.component will be having buttons like save, delete etc and while clicking "New" button I would like to call basewindow function ufbNew() after executing it should execute parent window function ufNew(). Please check my code and help me whether I'm doing it correctly. I'm able to call base function but parent not
//basewindow.component///
import { Component } from '#angular/core';
#Component({
selector: 'basewindow',
templateUrl: './Basewindow.component.html',
styleUrls: ['./Basewindow.component.css']
})
export class BasewindowComponent {
/////////////////////// User Base Functions ///////////////////
ufbNew() {
this.ufNew();
}
ufbSave() {
this.ufSave();
}
/////////////////////// User Functions for parent ///////////////////
ufNew() {
alert("I m In Base ufbNew")
}
ufSave() {
}
}
//// Basewindow.component.html
<div class="container">
<h1>Hero Form</h1>
<form>
<div class="form-group">
<button (click)="ufbNew()">New</button>
<button (click)="ufbSave()">Save</button>
</div>
</form>
</div>
/////////////////////////// AccountsCategory.Component (Parent 1) ////
import { Component } from '#angular/core';
import { BasewindowComponent } from '../base/basewindow.component';
#Component({
selector: 'app',
templateUrl: './AccountsCategory.component.html'
})
export class AccountsCategory extends BasewindowComponent {
/////////////////////// User Functions for parent ///////////////////
ufNew() {
alert("I m in Acounts category (parent)")
}
ufSave() {
}
}
//////////// AccountsCategory.component.html /////////////
<basewindow> </basewindow>
my purpose is to reuse base component objects , functions and override from child if requires.
please see test application in plnkr
https://plnkr.co/edit/bVxt4GjNXwIg7pDbR4sE?p=preview
Use this
abstract class Animal {
//OTHER STUFF
abstract ufSave(): void;
//OTHER STUFF
}

Resources