Angular binding #input behavior not working propertly - javascript

I'm having some issues with the Angular EventEmitter and angular #Input.
My app has 3 components: 2 components (TableComponent and MapComponent) that do not interact between them, and an extra component that is like the father of those two (BodyComponent).
TableComponent defines the following #Input
_oportunidades: Item[];
#Input() set oportunidades(oportunidades: Item[]){
debugger;
this._oportunidades = oportunidades;
this.dataSource = new MatTableDataSource<Item>(this._oportunidades);
this.dataSource.paginator = this.paginator;
}
The MapComponent defines:
#Output() event_emitter_for_items_filtered_by_polygon = new EventEmitter<string[]>();
send_new_map_information_to_body(){
this.event_emitter_for_items_filtered_by_polygon.emit(this.items_filtered_by_polygon);
}
add_function_that_sends_filtered_items_to_body_after_polygon_is_draw(){
var self = this;
google.maps.event.addListener(this.drawingManager, 'polygoncomplete', function(polygon) {
self.filter_items_by_polygon(polygon);
self.remove_markers_from_map();
polygon.setPath([])
self.send_new_map_information_to_body();
});
}
When the procedure send_new_map_information_to_body is triggered. Sends the modified data to the BodyComponent. The BodyComponent catches it without errors.
The BodyComponent html is shown here:
<div class="analysis">
<app-mapa (event_emitter_for_items_filtered_by_polygon)="items_filtered_by_polygon($event)" [items]="map_data"></app-mapa>
<app-tabla [oportunidades]="oportunidades_filtradas"></app-tabla>
</div>
The procedure items_filtered_by_polygon modifies the oportunidades_filtradas variable defined in the BodyComponent. Until now, everything is ok.
items_filtered_by_polygon($event){
this.oportunidades_filtradas = []
}
The variable oportunidades_filtradas is binded to the oportunidades variable in the TableComponent (as shown in the BodyComponent html), when items_filtered_by_polygon method changes oportunidades_filtradas the #Input() set oportunidades(oportunidades: Item[]) is not triggered. So, no changes are shown in my TableComponent.
When the app starts, and data is distributed through the components, everything works as expected. Just in this case, when trying to modify the TableComponent content as explained, nothing happens.
In the devtools console of chrome, no errors are shown. And the flow of the app does not feel strange, just nothing happens.
Sometimes, we thought that the modifications are being done, but maybe they are terrible delayed? Maybe is some kind of async issue?
I'm kind of new in Angular and maybe I am not understanding something. All other binds in my app are working...
What do you think? Any help is welcome!
Thanks!

This sounds like there may be a change detection issue happening. Depending on your change detection strategy things like this can happen. Try using ChangeDetectorRef.detectChanges() in your items_filtered_by_polygon function to see if that's the issue. If it works you can leave it there or remove it and use an observable for the input that isn't triggering.

Related

Angular - RxJS : afterViewInit and Async pipe

I tried to do the following in my component which uses changeDetection: ChangeDetectionStrategy.OnPush,
#ViewChild('searchInput') input: ElementRef;
ngAfterViewInit() {
this.searchText$ = fromEvent<any>(this.input.nativeElement, 'keyup')
.pipe(
map(event => event.target.value),
startWith(''),
debounceTime(300),
distinctUntilChanged()
);
}
And in the template
<div *ngIf="searchText$ | async as searchText;">
results for "<b>{{searchText}}</b>"
</div>
It doesn't work, however if I remove the OnPush, it does. I am not too sure why since the async pipe is supposed to trigger the change detection.
Edit:
Following the answers, I have tried to replace what I have by the following:
this.searchText$ = interval(1000);
Without any #Input, the async pipe is marking my component for check and it works just fine. So I don't get why I haven't got the same behavior with the fromEvent
By default Whenever Angular kicks change detection, it goes through all components one by one and checks if something changes and updates its DOM if it's so. what happens when you change default change detection to ChangeDetection.OnPush?
Angular changes its behavior and there are only two ways to update component DOM.
#Input property reference changed
Manually called markForCheck()
If you do one of those, it will update DOM accordingly. in your case you don't use the first option, so you have to use the second one and call markForCheck(), anywhere. but there is one occasion, whenever you use async pipe, it will call this method for you.
The async pipe subscribes to an Observable or Promise and returns the
latest value it has emitted. When a new value is emitted, the async
pipe marks the component to be checked for changes. When the component
gets destroyed, the async pipe unsubscribes automatically to avoid
potential memory leaks.
so there is nothing magic here, it calls markForCheck() under the hood. but if it's so why doesn't your solution work? In order to answer this question let's dive in into the AsyncPipe itself. if we inspect the source code AsyncPipes transform function looks like this
transform(obj: Observable<any>|Promise<any>|null|undefined): any {
if (!this._obj) {
if (obj) {
this._subscribe(obj);
}
this._latestReturnedValue = this._latestValue;
return this._latestValue;
}
....// some extra code here not interesting
}
so if the value passed is not undefined, it will subscribe to that observable and act accordingly (call markForCheck(), whenever value emits)
Now it's the most crucial part
the first time Angular calls the transform method, it is undefined, because you initialize searchText$ inside ngAfterViewInit() callback (the View is already rendered, so it calls async pipe also). So when you initialize searchText$ field, the change detection already finished for this component, so it doesn't know that searchText$ has been defined, and subsequently it doesn't call AsyncPipe anymore, so the problem is that it never get's to AsyncPipe to subscribe on those changes, what you have to do is call markForCheck() only once after the initialization, Angular ran changeDetection again on that component, update the DOM and call AsyncPipe, which will subscribe to that observable
ngAfterViewInit() {
this.searchText$ =
fromEvent<any>(this.input.nativeElement, "keyup").pipe(
map((event) => event.target.value),
startWith(""),
debounceTime(300),
distinctUntilChanged()
);
this.cf.markForCheck();
}
The changeDetection: ChangeDetectionStrategy.OnPush allow to the component to not triggered the changeDetection all the time but just when an #Input() reference is updated. So if you do all your stuff in the same component, no #Input() reference are updated so the view is not updated.
I propose you to Create your dumb component with your template code above, but give it the searchText via an #Input(), and call your dumb component in your smart component
Smart component
<my-dumb-component [searchText]="searchText$ | async"></my-dumb-component>
Dumb component
#Input() searchText: SearchText
template
<div *ngIf="searchText">
results for "<b>{{searchText}}</b>"
</div>
This is because Angular is updates DOM interpolations before ngAfterViewInit and ngAfterViewChecked. I know this sounds confusing a bit. It's because of the first change detection cycle Angular does. Referring to Max Koretskyi's article about change detection algorithm of Angular, in a change detection cycle these happens sequentially:
sets ViewState.firstCheck to true if a view is checked for the first time and to false if it was already checked before
checks and updates input properties on a child component/directive
instance
updates child view change detection state (part of change detection
strategy implementation)
runs change detection for the embedded views (repeats the steps in
the list)
calls OnChanges lifecycle hook on a child component if bindings
changed
calls OnInit and ngDoCheck on a child component (OnInit is called
only during first check)
updates ContentChildren query list on a child view component
instance
calls AfterContentInit and AfterContentChecked lifecycle hooks on
child component instance (AfterContentInit is called only during
first check)
updates DOM interpolations for the current view if properties on
current view component instance changed
runs change detection for a child view (repeats the steps in this
list)
updates ViewChildren query list on the current view component
instance
calls AfterViewInit and AfterViewChecked lifecycle hooks on child
component instance (AfterViewInit is called only during first
check)
disables checks for the current view (part of change detection
strategy implementation)
As you see, Angular updates DOM interpolations (at step 9) after AfterContentInit and AfterContentChecked hooks are called, so if you call rxjs subscriptions in AfterContentInit or AfterContentChecked lifecycle hooks (or earlier, like OnInit etc.) your DOM will be updated because Angular updates DOM at step 10, and when you change something in ngAfterViewInit() and you are using OnPush, Angular won't update DOM because you are at step 12 on ngAfterViewInit() and Angular has already updated DOM before you change something!
There are workaround solutions to avoid this to subscribe it in ngAfterViewInit. First, you can call markForCheck() function, so you basically say by using it on the first cycle that "hey Angular, you updated DOM on step 9, but I have something to change at step 12, so please be careful, have a look at ngAfterViewInit I have still something to change". Or as a second solution, you can trigger a change detection manually again (by triggering and event handler or using detecthanges() function of ChangeDetectorRef) so that Angular repeats all these steps again, and when it reaches at step 9 again, Angular updates your DOM.
I have created a Stackblitz example that you can try these out. You can uncomment the lines of subscriptions placed in lifecycle hooks 1 by 1, so that you can see after which lifecycle hook Angular updates DOM. Or you can try triggering an event or triggering change detection cycle manually and see that Angular updates DOM on the next cycle.

using a destroyed view for changeDetection in ngAfterViewInit

A private (but open source) Angular package in the organisation I work for has some code that looks like this:
ngAfterViewInit(): void {
setTimeout(() => {
this.changeDetector.detectChanges();
// do more things
});
}
We implement this package in our application (the package is to introduce common components so that front-end devs across the org don't implement things multiple times, and to enforce a common design language for consistency). However, when using the component this is called in, and showing and then destroying the component repeatedly, eventually it will stop working, with this error in the console:
Error: ViewDestroyedError: Attempt to use a destroyed view: detectChanges
I'm trying to find a way of making sure the code above is not being run if the component has already been destroyed. I have considered using the ngOnDestroy lifecycle method:
ngOnDestroy(): void {
this.changeDetector.detach();
}
but I'm not sure what else I would put in there to check the view is ok. I also thought about something like:
if (!this.changeDetector.destroyed) {
this.changeDetector.detectChanges();
}
but nothing like destroyed exists on ChangeDetectorRef.
What is the correct way of ensuring this error does not show and the component works, even when repeatedly showing and destroying it?
I think the best solution I have found is to use !ViewRef.destroyed and if that returns truthy then proceed, otherwise don't.
if (!(this.changeDetector as ViewRef).destroyed) {
this.changeDetector.detectChanges()
// do other tasks
}
I think destroyed does work but the syntax is like this:
if (!this.changeDetector['destroyed']) {
this.changeDetector.detectChanges();
}
Then obviously, just keep the code you have on your ngOnDestroy hook.

Why does component view update when change detection is set to onPush? [duplicate]

I thought I was pretty clear on how Angular Change detection works after this discussion: Why is change detection not happening here when [value] changed?
But take a look at this plunk: https://plnkr.co/edit/jb2k7U3TfV7qX2x1fV4X?p=preview
#Component({
selector: 'simple',
template: `
<div (click)="onClick()">
{{myData[0].name}}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Simple {
public #Input() myData;
constructor() {
}
public onClick() {
}
}
Click on a, it's changed to c
I understand that the click event triggers change detection on the App level, but [myData]="testData" is still referring to the same object, and I am using On Push on Simple, why does a get changed?
That's by design.
If you have component with OnPush change detection then its detectChangesInternal function won't be triggered unless one of four things happens:
1) one of its #Inputs changes
~2.4.x
~4.x.x
Note: #Inputs should be presented in template. See issue https://github.com/angular/angular/issues/20611 and comment
2) a bound event is triggered from the component (that is your case)
Caveats: There is some difference here between 2.x.x and 4
Angular ChangeDetectionStrategy.OnPush with child component emitting an event
~2.4.x
~4.x.x
3) you manually mark the component to be checked (ChangeDetectorRef.markForCheck())
4) async pipe calls ChangeDetectorRef.markForCheck() internally
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();
}
}
https://github.com/angular/angular/blob/2.4.8/modules/%40angular/common/src/pipes/async_pipe.ts#L137
In other words if you set OnPush for component then after the first checking component's status will be changed from CheckOnce to Checked and after that it's waiting as long as we do not change status. It will happen in one of three things above.
See also:
https://github.com/angular/angular/issues/11678#issuecomment-247894782
There are also good explanations of how angular2 change detection work:
https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
Here is Live Example(Thanks to Paskal) that explains onPush change detection. (Comp16 looks like your component. You can click at this box).

Angular2, doe's EventEmitter require zone.run?

I'm using ionic2, I implemented a class:
import {EventEmitter, Injectable} from 'angular2/core';
#Injectable()
export class LocalPushClear extends EventEmitter<number> {
constructor() {
super();
}
}
The class is used by on of my components to connect cordova plugin event to another component which subscribe to LocalPushClear, I listen to clear events, ones it fires, I emit using LocalPushClear and some other component subscribes:
this._LocalPushClear.subscribe(data => {
// Some action is taken here
});
The thing is that, I was expecting automatic change detection to be executed upon subscription callback execution(when its done), but it seems like there is no change detection execution at all, I have to do something like click a button or wrap my Some action with zone.run, I'm not sure if its a valid behavior or maybe I'm doing something wrong.
Edit:
I traces the code and it leads to Subject, so its basically custom event emitter that angular NgZone don't know about(at least I think), but I'm sure, if anyone could confirm, maybe future explain I will be very thankful.
You definitely should not extend EventEmitter. EventEmitter is only supposed to be used for #Output()s. Just use a Subject instead.
Angular doesn't get notified about values emitted by EventEmitter (when used this way) or Subject. Normally the code that causes the Observable (Subject) to emit new values is executed by code that causes change detection when completed for example when called from an event handler or setTimeout.
In your case the cause seems to be that the code that emits new values using LocalPushClear runs outside Angulars zone.
You can use one of the methods explained in https://stackoverflow.com/a/34829089/217408 to trigger change detection after the Observable emits an event.

Angular2 change detection not working after callback

I am quite new to angular2 and I have a problem with the change detection.
At the loading of my page, I need to call some API in order to get the information to construct my web page. What I do is that when I receive this information (which is contained in an array) I want to iterate through it using *ngFor. This is my code for a course component.
import {Component,Input} from 'angular2/core';
import {courseCompDiagram, sepExInWeeks} from "../js/coursesTreatment.js";
import {getSampleWeeks} from "../js/courseMng.js";
#Component({
selector: 'course',
directives:[Exercises],
template: `
<div class="course">
<h2>{{aCourse.name}}</h2>
<div class='diag-container row'>
<div id="Completion{{aCourse.name}}"></div>
<div *ngFor="#week of weeks"> {{week.weekNb}} </div>
</div>
</div>`
})
export class Course{
//This is inputed from a parent component
#Input() aCourse;
this.weeks = [];
ngAfterViewInit(){
//I call this method and when the callbacks are finished,
//It does the following lines
courseCompDiagram(this.aCourse, function(concernedCourse){
//When my API call is finished, I treat the course, and store the results in weeks
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
//This is not supposed to stay in my code,
//but is here to show that if I call it here,
//the weeks will effectively change
this.weeks = getSampleWeeks();
}
}
So first of all, I would like to know if it's normal that angular2 doesnt detect the fact that this.weeks changed.
Then I don't know if I should us the ngAfterViewInit function to do my work in. The problem is that I began doing that because in my courseCompDiagram I need to use jquery to find the div containing the id Completion[...] and modify it (using highcharts on it). But maybe I should do all this at some other point of the loading of the page ?
I tried using ngZone and ChangeDetectionStrategy as stated in this topic but I didn't manage to make it work on my case.
Any help is appreciated, even if it doesn't completely solves the problem.
export class Course{
//This is inputed from a parent component
#Input() aCourse;
this.weeks = [];
constructor(private _zone:NgZone) {}
ngAfterViewInit(){
//I call this method and when the callbacks are finished,
//It does the following lines
courseCompDiagram(this.aCourse, (concernedCourse) => {
//When my API call is finished, I treat the course, and store the results in weeks
this._zone.run(() => {
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
});
//This is not supposed to stay in my code,
//but is here to show that if I call it here,
//the weeks will effectively change
this.weeks = getSampleWeeks();
}
}
You should use arrow functions to be able to use lexical this, as described below:
courseCompDiagram(this.aCourse, (concernedCourse) => {
// When my API call is finished, I treat the course,
// and store the results in weeks
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
As a matter with raw callbacks, the this keyword doesn't correspond to your component instance.
See this link for more hints about the lexical this of arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions.
Otherwise I have a sample comment regarding your code. You should leverage observables for your HTTP calls. It doesn't seem the case in your code as far as I can see...

Categories

Resources