I have a component with a viewChild() binding to a child component that I use to call a function in the child component. The function takes an argument of true or false and is meant to set a class variable in the child component to the argument's value. The class variable is then meant to be read in an if statement in another function in the child component.
So far, I have successfully called the child component function from the parent component, passed the boolean argument to it, set the class variable, and printed it to the console. I have verified that the class function is included in the 'this' of the component.
IN THE PARENT COMPONENT:
if (res[0] === undefined) {
this.typeAhead.badRequest(true);
}
IN THE CHILD COMPONENT:
The console log in the onSubmit() function only returns the value of _badRequestFlag set when the variable was declared, not the value assigned in badRequest().
private _badRequestFlag: boolean = false;
badRequest (res: boolean) {
this._badPlaRequestFlag = res;
}
onSubmit (): void {
console.log('BAD PLA REQUEST:, ', this);
if (this._badRequestFlag === true) {
alert('Does Not Exist');
throw (new Error('Does Not Exist'));
}
When I try to use the class variable in the onSubmit() function, it's value is only the value that it was declared with, and not the value set in the badRequest() function. I'm assuming that I'm running into a scoping issue, but I cannot determine what and how to resolve it.
I would recommend setting the badRequestFlag using an input instead:
class ChildComponent {
#Input() badRequestFlag: boolean;
}
#Component({
selector: 'app',
template: `
<child-component [badRequestFlag]="true"></child-component>
`
})
You can bind this to a variable in the parent controller: [badRequestFlag]="requestFlag"
probably you should review the lifecycle-hooks, i try to understand you problem in stackblitz but i can't replay your decribed error. i hope help you
Related
In a nutshell, I have a component which works as a text input. The parent component passes data into this component using #Input bindings. When the change event gets fired via the #Output binding, I perform some automatic validation in the parent component. This is to remove erroneous values and replace them with some sensible default for a better user experience.
Here is a basic version of the parent component
#Component({
selector: 'parent',
template: `
<div>
<child [value]="item.key1"
(valueChange)="onValueChange($event)">
</child>
</div>
`
})
export class Parent {
item = {
key1: 'test value',
key2: 'another test value'
};
onValueChange(newValue) {
// Perform some validation and set default
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
this.item.key1 = newValue;
}
}
And the child component
#Component({
selector: 'child',
template: `
<div>
<input type="text"
[(ngModel)]="value"
(blur)="onBlur($event)" />
</div>
`
})
export class Child {
#Input() value: string;
#Output() valueChange = new EventEmitter();
onBlur() {
this.valueChange.emit(this.value);
}
}
See here for a Plunker example
The issue I am having is as follows:
When entering a value in the child input and firing the blur event, the new value is bubbled up to the parent and the validation is applied - if the validation causes the value to get modified, it bubbles back down to the child's value correctly - happy days.
However, if the first 4 characters stay the same, and you append additional characters, upon blur the validation will still be applied and the parent's value will get updated correctly, but the child will preserve the "invalid" value - no more happy days.
So it looks to me like Angular isn't detecting the change in the parents data (fair enough because it hasn't technically changed) so it isn't sending the "latest" value back down to the child.
My question is, how can I get the child text input to always show the correct value from the parent, even if it technically hasn't "changed"?
better solution
#Jota.Toledo's good comment made me realise that my approach, although it did serve as a quick workaround for me at the time, it's not a good one, so I actually went and made some changes to my project that can work for you as well, also following his suggestion of
Delegating that "validation" logic into the child component
while keeping the validation definition in the parent as a function that's passed to the child as an #Input param.
This way I'd give the parent 2 public vars
a object (item)
a function (validation)
and change onValueChange function to only update the item.key1 as it will be already validated.
In the child add a new #Input param (validation) of type Function and use that function to validate the newValue inside the onBlur, before emiting the value to the parent.
I have the feeling that what I've written here might "sound" a bit confusing so I'm adding the code for what I'm trying to explain.
Parent
#Component({
selector: 'parent',
template: `
<div>
<p><b>This is the parent component</b></p>
<child [value]="item.key1"
[validation]="validation"
(valueChange)="onValueChange($event)">
</child>
<p><b>Variable Value:</b> {{item | json}} </p>
</div>
`
})
export class Parent {
item = {
key1: 'test value',
key2: 'another test value'
};
validation = (newValue) => {
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
return newValue;
}
onValueChange(newValue) {
this.item.key1 = newValue;
}
}
Child (leaving the template part out because it's unchanged)
export class Child {
#Input() value: string;
#Input() validation: Function;
#Output() valueChange = new EventEmitter();
onBlur() {
this.value = this.validation(this.value)
this.valueChange.emit(this.value);
}
}
previous answer (not a good approach)
I had a similar problem in the past and the way I solved was to clear my var and then giving it the final value inside a setTimeout without specifying the milliseconds.
in your parent component it would look something like this:
onValueChange(newValue) {
console.log('Initial: ' + newValue);
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
console.log('Final: ' + newValue);
this.item.key1 = '';
setTimeout(() => {
this.item.key1 = newValue;
});
}
However, if the first 4 characters stay the same, and you append
additional characters, upon blur the validation will still be applied
and the parent's value will get updated correctly, but the child will
preserve the "invalid" value - no more happy days.
So it looks to me like Angular isn't detecting the change in the
parents data (fair enough because it hasn't technically changed) so it
isn't sending the "latest" value back down to the child.
Correct. The fact is that angular is "caching" the latest value passed through the value input property, and as you recognize, you arent really pushing new values into it if you dont change the first 4 chars.
You can check this by adding ngOnChanges(changes) to your child and logging the changes value into console; in the above case nothing is logged, as no new value for value is pushed through.
You could overcome this by:
Enforcing that a new value is always pushed through the input property by wrapping the value in an object and unwrapping it in the child component. (Bad approach IMO)
Delegating that "validation" logic into the child component, so that only "validated" values are emitted through the output property.
You should create a Subject on the ParentComponent :
export class ParentComponent {
parentSubject:Subject<Item> = new Subject();
notifyChildren() {
this.parentSubject.next('new item value');
}
}
Pass it as Input() on the ChildComponent :
<child [parentSubject]="parentSubject"></child>
And finally subscribe to it on the ChildComponent :
export class ChildComponent {
#Input() parentSubject:Subject<Item>;
ngOnInit() {
this.parentSubject.subscribe(event => {
//do your stuff with the updated value
});
}
you can update your code for onValueChange method in parent.ts with following
onValueChange(newValue) {
this.item.key1 = null;
setTimeout(() => {
console.log('Initial: ' + newValue);
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
console.log('Final: ' + newValue);
this.item.key1 = newValue;
})
}
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;
}
What I'm trying to do ?
I have a parent component(say parent) which contains 2 children components(say child-1 and child-2). When one of the DOM element value within child 1 changes, I'm assigning the new value to one of the properties of an object and emitting the object to parent. Parent then assigns the incoming object to its own local object, which I'm passing as an input([setupValues]) to child 2 component. Within Child 2, when there is any change to that #Input object, I want to assign one of its properties to a local variable(which is not happening because the child 2 Onchanges doesn't get fired even when one of the properties within #Input object has changed).
My Source Code(most removed due to brevity)
Consider I have an interface ISetups.ts
export interface ISetups {
DOB: string;
DOD: string;
DOE: string;
}
Markup of parent.html goes as follows
<child-1 [input]="someVariable" (setupsChanged)="onSetupsChange($event)">
</child-1>
{{ allSetups.DOB }} <!-- the change is shown correctly here -->
<child-2 [setupValues]="allSetups"></child-2>
In parent.ts
export class ParentComponent {
someVariable: string;
allSetups: ISetups;
onSetupsChange(allSetups: ISetups) {
// emitted object from child 1 is assigned to local object
this.allSetups = allSetups;
}
}
In child-1.html
<input class="form-control" [(ngModel)]="ownDOB" (blur)="emitChange()" >
In child-1.ts
export class Child1Component {
#Input() input: string;
#Output() setupsChanged: EventEmitter<ISetups> = new EventEmitter<ISetups>();
allSetups: ISetups;
ownDOB: string;
emitChange() {
this.allSetups.DOB = this.ownDOB;
this.setupsChanged.emit(this.allSetups); //emitting object to parent
}
}
In child-2.html
<p>{{ localDOB }}</p>
In child-2.ts
export class Child2Component implements OnChanges {
localDOB: string;
#Input() setupValues: ISetups;
ngOnChanges(): void {
console.log("this does not fire if setupValues.DOB changes");
this.localDOB = this.setupValues.DOB;
}
}
What worked ?
Passing the property directly as another input worked
<child-2 [DOB]="allSetups.DOB" [setupValues]="allSetups"></child-2>
The above works but obviously its not a nice way & I don't want to do this.
The above proves that when I send the variable on its own, it fires the OnChanges but doesn't fire when just one of the properties within an object is changed.
Any help is much appreciated.
this is happened because your reference to object is not changed, so if in your parent.ts change your code to this, it should work. By this way (using spread operator) you are creating a new instance of object
onSetupsChange(allSetups: ISetups) {
// emitted object from child 1 is assigned to local object
this.allSetups = {...allSetups};
}
I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}
In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}
Im using Angular 1.5.3 with typescript.
I have a outer and a inner component where the outercomponents holds an array named arrayValue which I pass to the innercomponent via <binding:
class InnerComponent {
controller: Function = InnerController;
bindings: any = {
arrayValue : "<"
};
...
}
The InnerController uses the $onChanges method to track any changes from one-way bindings (e. g. arrayValue):
public $onChanges(changes: any){
this.onChangesCalledCounter++;
console.log(changes);
}
If I now change the arrayValue within the outer component using:
public switchArrayValue(): void {
if(this.arrayValue === this.fruits){
this.arrayValue = this.vegetables;
} else {
this.arrayValue = this.fruits;
}
}
The $onChanges within the innercomponent gets called. However, If I change the switchArrayValue method to perform a push instead of a reassignment of the array, the $onChanges method won't get called:
public switchArrayValue(): void {
this.arrayValue.push("hello, world!");
}
Can anyone tell me why the push don't trigger the $onChanges and maybe show a workaround for that?
Here is a plnkr (that I forked).