Angular2, doe's EventEmitter require zone.run? - javascript

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.

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.

Angular2+: how does NgModel / NgControl internally handle updates from view to model?

I am doing a deep dive into how two-way databinding works. I am currently puzzled by how updates from the view (say, an input element) propagate to NgControl internally.
In the definition of ControlValueAccessor it mentions that registerOnChange is responsible for view -> model updates (docs where they say it, and src). With a simple directive that we may put on the same input element as [(NgModel)], e.g. <input [(NgModel)]=stuff myInspectorDirective>, I tried playing around with this.
constructor(private ngControl: NgControl) { }
ngOnInit(): void {
// this.ngControl.valueAccessor['onChange'] = () => {};
// uncommenting the above line prevents updates from view to model
}
Uncommenting/commenting the indicated line allows us to allow/block updates from the input element to the model. But I'm puzzled by this because in the source code of DefaultValueAccessor, the one used in this example, onChange is not really doing anything: (_:any) => {}.
So, I would expect that under the hood, e.g. in ng_model.ts or in one of the related classes, like NgControl or FormControl, something happens with the onChange function from the ValueAccessor; setting it or wrapping it in another function, maybe a proxy, or whatever. I did not find anything. Then I went on looking for some code where listeners (for the input event, more explicitly) are explicitly bound to the input element, but no luck either.
I noticed that the OnChanges function calls _setValue, but I'm not sure if I'm going in the right direction when diving into the internals of change detection, as I would expect the listening to changes in the DOM to be related to ControlValueAccessors and/or FormControl/AbstractControl
Anyone feels like elaborating on how this works? :-)
The ControlValueAccessor.registerOnChange is provided by the NgForm.
1) NgModel is registered in NgForm (see https://github.com/angular/angular/blob/master/packages/forms/src/directives/ng_model.ts)
in NgModel.ngOnChanges: this._setUpControl calls this.formDirective.addControl
2) NgForm calls shared setUpControl function (see https://github.com/angular/angular/blob/master/packages/forms/src/directives/ng_form.ts)
import { setUpControl } from './shared';
NgForm.addControl calls setUpControl
3) setUpControl registers change event handler (see https://github.com/angular/angular/blob/master/packages/forms/src/directives/shared.ts)
setUpControl calls setUpViewChangePipeline
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
dir.valueAccessor !.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingChange = true;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}

Angular binding #input behavior not working propertly

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.

Change detection cycle in Angular - clarification?

Regarding ChangeDetectorRef, I already know that -
detectChanges actually triggers change detection while with -
markForCheck - The actual change detection for the component is not
scheduled but when it will happen in the future (either as part of the
current or next CD cycle)
Taken from here
Looking at markForCheck - if it's not scheduled, so when will it run? Obviously after Observables callback, async callbacks and setTimout and events.
The docs has a component with OnPush strategy
#Component({
selector: 'cmp',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `Number of ticks: {{numberOfTicks}}`
})
class Cmp {
numberOfTicks = 0;
constructor(private ref: ChangeDetectorRef) {
setInterval(() => {
this.numberOfTicks++;
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
}, 1000);
}
}
Question:
If it marks for checking its ancestors, for the next cycle, so who ran the current cycle? (Is it the prev setTimeout invocation?)
Because this code is showing each second being replaced by another - in other words each second there is a change detection (?!).
What is actually going on here via a POV of steps?
markForCheck, as you guessed, and as the name suggests, will tell the Angular to mark the component to be change-detectable for the next cycle.
When you're writing setInterval, this is what you're actually writing:
constructor(private ref: ChangeDetectorRef) {
setInterval(() => {
this.numberOfTicks++;
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
detectChangesFromZoneJS(); // this is a psudo code, but you can imagine something like this will happen, which is patched by NGZone
// Note that this is the last function that will always be called by Zone at the end of all the async events
}, 1000);
}
This is handled by ZoneJS.
Zone monkey patches all of the async events, and when they finish, Zone will notify Angular and then Angular. knows it's time to detect the changes ( update the view based on the latest model changes), but when you're component is OnPush is not going to detect the changes, unless there has special things happened inside the component, ( like a click event, or any of the #input's is changed).
So when you're deliberately saying markForCheck, you're basically saying: "I know you shouldn't detect the changes because it's OnPush only, but I'm telling you to detect it anyway"
So here is step by step:
Component is initialized, Angular will detect all the changes, your view is updated
You run a setInterval, inside it, you're mutating your model, Angular knows it shouldn't update the view because it's OnPush
the first interval's callback gets called, we're inside the first function, you're marking the component to be deliberately checked, we're at the end of the interval function ( still the first one interval).
Zone notify's Anguar that an Async event is just finished, time to detect the changes
Angular looks at the OnPush and wants to ignore it, but remembers that you've marked the component to be checked by force
the view get's updated
we go to the second interval and so on.

Preventing Circular Dependencies with Exported Singleton Class

I have a question regarding a scenario I keep running into building HTML5 games resulting in difficult to manage circular dependencies.
I understand completely why the circular dependency is occuring and where it is occurring. However, I can't seem to figure out a convenient way to get around it, so I assume my logic / approach is fundamentally flawed.
Here's a little bit of context.
I have a game that has a single point of entry (compiled with Webpack) called Game.js. I have a basic event manager that allows for two functions on(key, callback) and fire(key, parameters).
The event manager simply creates an object, sets the supplied key of on as a property with an array value populated with any callback functions registered to that key. When the fire method is called that property is retrieved and all of the fuctions defined in it's array value are invoked.
What I'm trying to do
I want to be able to instance the event manager on Game.js and export an instance of Game that other classes can import and subsequently register callbacks to the Game instances event manager.
class Game {
constructor() {
this.events = new EventManager();
window.addEventListener('resize', this.resize.bind(this));
}
resize(event) {
if(window.innerWidth < window.innerHeight) {
this.events.fire('orientation-change', 'vertical');
} else {
this.events.fire('orientation-change', 'horizontal');
}
}
}
export default new Game();
Then for example a Button class may need to respond to an orientation change event fired by the Game. Please note the above is simply an example of a circumstance in which the event manager may fire an event, but this condition could be anything.
import Game from '../core/Game';
class Button {
constructor() {
Game.events.on('orientation-change', this.reorient.bind(this));
}
reorient() {
// ...
}
}
export default Button;
The above class is a UI component called Button that needs to know when the orientation-change event is fired, again please note this event could be anything.
What's the problem?
Nothing looks particularly wrong with the above, however, because Game.js is the entry point, at some point an instance of Button is created whether it be directly in Game.js or through another class which is subsequently instanced via Game.js which of course causes a circular dependency because even if not directly, Game imports Button and Button imports Game.
What I've tried
There are two main solutions that I have found that work (to some degree). The first being simply waiting for the export to be available using an interval check of the value of Game in the constructor of Button, like this:
import Game from '../core/Game';
class Button {
constructor() {
let check = setInterval(() => {
if(Game !== undefined) {
Game.events.on('orientation-change', this.reorient.bind(this));
clearInterval(check);
}
}, 100);
}
reorient() {
// ...
}
}
export default Button;
This will typically resolve in a single iteration.
The second solution being to use dependency injection and pass reference of Game to Button when it's instanced, which again works great, but the prospect of having to repeatedly do this per class seems unintuitive. The interval check works fine too, but seems hacky.
I'm feel like I'm completely missing something and that the solution isn't a difficult as I'm making it.
Thanks for any help regarding this.

Categories

Resources