I'm developing an app in ng2 and I'm struggling with something. I'm building a calendar where you can pick a date-range and I need to react on click & mouseenter/mouseleave events on day cells. So I have a code (simplified) like this:
calendar.component.html
<month>
<day *ngFor="let day of days" (click)="handleClick()"
(mouseenter)="handleMouseEnter()"
(mouseleave)="handleMouseLeave()"
[innerHTML]="day"></day>
</month>
But this gives me hundreds of separate event listeners in the browser's memory (each day's cell gets 3 event listeners, and I can have up to 12 months displayed at a time so it would be over 1k of listeners).
So I wanted to do it "the proper way", using the method called "event delegation". I mean, attach a click event on the parent component (month) and when it receives a click event, simply check whether it occurred on Day component - and only then I would react to this click. Something like jQuery does in it's on() method when you pass it the selector parameter.
But I was doing it by referencing the DOM elements natively in the handler's code:
month.component.ts
private handleClick(event) {
if (event.target.tagName === 'DAY') {
// handle day click
} else {
// handle other cases
}
}
and my colleagues rejected my idea, since - as they said - "There must be a simpler, proper way of handling this in NG2; like there is in jQuery. Besides, it's getting out of control here - you're reacting to Day's clicks in Month's code."
So, my question is, is there a better way? Or am I trying to solve a problem which I shouldn't bother solving anymore, since users' devices get more and more memory/processing power everyday?
Thanks in advance!
Intro
I stumbled across this today, and I can really see the need for this implementation in a lot of applications. Now I can't vouch that this is 100% the best technique however I have gone out of my way to make this approach as angular inspired as possible.
The approach that I have come up with has 2 stages. Both stage 1 and stage 2 will add a total of years * months + years * months * days, so for 1 year you will have 12 + 365 events.
Stage Scope
Stage 1: Delegate events from when a month is clicked down into the actual day which was clicked without requiring an event on the day.
Stage 2: Propagate the chosen day back to the month.
Just before delving in, the application consists of 3 components which are nested in the following order: app => month => day
This is all the html that is required. app.component hosts a number of months, month.component hosts a number of days and day.component does nothing but display it's day as text.
app.component.html
<app-month *ngFor="let month of months" [data-month]="month"></app-month>
month.component.html
<app-day *ngFor="let day of days" [data-day]="day">{{day}}</app-day>
day.component.html
<ng-content></ng-content>
This is pretty stock standard stuff.
Stage 1
Let's have a look at the month.component.ts where we want to delegate our event from.
// obtain a reference to the month(this) element
constructor(private element: ElementRef) { }
// when this component is clicked...
#HostListener('click', ['$event'])
public onMonthClick(event) {
// check to see whether the target element was a child or if it was in-fact this element
if (event.target != this.element.nativeElement) {
// if it was a child, then delegate our event to it.
// this is a little bit of javascript trickery where we are going to dispatch a custom event named 'delegateclick' on the target.
event.target.dispatchEvent(new CustomEvent('delegateEvent'));
}
}
In both stage 1 and 2, there is only 1 caveat and that is; if you have nested child elements within your day.component.html, you will need to either implement bubbling for this, better logic in that if statement, or a quick hack would be.. in day.component.css :host *{pointer-events: none;}
Now we need to tell our day.component to be expecting our delegateEvent event. So in day.component.ts all you have to do (in the most angular way possible) is...
#HostListener('delegateEvent', ['$event'])
public onEvent() {
console.log("i've been clicked via a delegate!");
}
This works because typescript doesn't care about whether the event is native or not, it will just bind a new javascript event to the element and thus allows us to call it "natively" via event.target.dispatchEvent as we do above in month.component.ts.
That concludes Stage 1, we are now successfully delegating events from our month to our days.
Stage 2
So what happens if we say want to run a little bit of logic in our delegated event within day.component and then return it to month.component - so that it can then carry on with its own functionality in a very object oriented method? Well fortunately, we can very easily implement this!
In month.component.ts update to the following. All that has changed is that we are now going to pass a function with our event invocation and we defined our callback function.
#HostListener('click', ['$event'])
public onMonthClick(event) {
if (event.target != this.element.nativeElement) {
event.target.dispatchEvent(new CustomEvent('delegateEvent', { detail: this.eventDelegateCallback}));
}
}
public eventDelegateCallback(data) {
console.log(data);
}
All that is left is to invoke this function within day.component.ts...
public onEvent(event) {
// run whatever logic you like,
//return whatever data you like to month.component
event.detail(this.day);
}
Unfortunately our callback function is a little ambiguously named here, however typescript will complain about the property not being a defined object literal for CustomEventInit if named otherwise.
Multiple Event Funnel
The other cool thing about this approach is that you should never have to define more than this number of events because you can just funnel all events through this delegation and then run logic within day.component.ts to filter by event.type...
month.component.ts
#HostListener('click', ['$event'])
#HostListener('mouseover', ['$event'])
#HostListener('mouseout', ['$event'])
public onMonthEvent(event) {
if (event.target != this.element.nativeElement) {
event.target.dispatchEvent(new CustomEvent('delegateEvent', { detail: this.eventDelegateCallback }));
}
}
day.component.ts
private eventDelegateCallback: any;
#HostListener('delegateEvent', ['$event'])
public onEvent(event) {
this.eventDelegateCallback = event.detail;
if(event.type == "click"){
// run click stuff
this.eventDelegateCallback(this.day)
}
}
Related
How to hold event triggering in angular.
This question related with my another question : how to stop array.filter() function
I have tried those all answers. But am not satisfied with that answers.
So I did something from my own idea.
Problem:
I have a ngmodelchange event and that is for calling a method. You can see my latest question for why am using ngModelChange() event. Anyway I will explain here also.
Am search and filtering large amount of data using ngModelChange event. So the problem is when I typing character's continuously the application is hanging.
My Suggestion
So I have suggested myself for use some logic to call the filter function after user has stop the typing. But we don't know when the user will stop typing.
So I create a logic below.
What am Did
(ngModelChange)="clearSearch(gloablFilterValue)"// html side
In .ts side
clearSearch(newValue: string) {
this.oldFilterText = newValue;
if (this.isLooping) {
return false;
}
for (let i = 0; i > newValue.length + 1; i++) {
this.isLooping = true;
if (this.oldFilterText == newValue) {
this.splitCustomFilter();//filter function
}
}
}
The above code is not working. am just starting to write logic. So if some have any idea. please come with me you suggestion and ideas to achieve this.
Main question
How can we stop the event triggering until the user has typing values in text box? And how can we start the event triggering the user has typed values in textbox?
**Important: I don't want to use focus out and blur events. because the filter should works while the user typing the values.
What you're looking for is called deboucing. A good alternative is throttling. RxJS supports has both operators, called debounceTime and throttleTime. Here's a quick example of how to utilize them.
In the template, listen for the input event (or some other event, based on what you're listening to in your use-case).
<input type=text (input)="onInput($event.target.value)">
In your component code, create a subject and then use the appropriate operator on it before you subscribe to it.
// create a subject
private subject = new Subject<string>()
// function to call when event fires
public onInput(input: string) { this.subject.next(input) }
ngOnInit() {
// debounce the stream
this.subject.debounceTime(300).subscribe(inputText => {
// this is where you can do you .filter
console.log(inputText)
})
}
Of course, you can tweak the 300 parameter as you wish.
I am trying to learn Observer and publisher-subscriber pattern.
came through this simple example here
Problem: There is a button and onclick of the button it should be updating the count.
without any pattern i can do simply as
window.onload = function() {
var container = document.querySelector('.container');
var count = 0;
container.querySelector('#click').addEventListener('click', function() {
count = +document.querySelector("#count").innerHTML;
count++;
document.querySelector("#count").innerHTML = count;
});
}
<div class="container">
<input type="button" id="click" value="click">Total Counts: <span id="count">0</span>
</div>
In the above link that i have shared about observer pattern it has an implementation for the same using observer pattern jsbin
My Question here, is the usage of a pattern not complicating the code. I am really having a bad time of understanding what exactly the code is trying to solve .can some one please explain this and what is this.notify doing in the jsbin code.
Please help
Thanks
Not an expert in patterns but from what I understand, with simple code like your example that takes in a single event listener, the Observer Pattern would definitely be overkill.
As explained in your link above: "The observer pattern is a simple way to allow communication between elements without having to rely on events, callbacks, or polling. The best thing about the observer pattern is that the thing being observed does not have to worry about what is observing it or how many observers it has." It basically allows you to attach observers easily without having to modify the base element code, because the base code doesn't really have to care about who is watching it. It just has to announce that it's done something (increased a counter property) and it's up to the observers to react accordingly. Because of this, the counter code could stand on it's own and not have any dependencies to run (thus, making it easier to test as well). If you need to make changes to your observers, you won't have to touch the counter code and risk causing any side effects.
In comparison, your example has your callback code and counter heavily tied to one another. If you need to make a change like say, making it have different wording or have the counter value appear under a specific element, you have no choice but to touch that entire block of code. Again though, your code example is simple enough and if that is all it will be doing, then it should be perfectly fine to use.
I think it's easier to understand the concept of the Observer pattern when working with stuff like async code and Promises, where your callbacks/observers become separate from your implementing async code
Firstly, please make sure we are on the same page regarding the terminologies in Observer Pattern (OP): Observer object, Subject (or Observee) object, Subject.addObserver(...) method, and Subject.notify(...) method.
OK, now,
without any pattern i can do simply as
No, you are actually using OP in an implicit form. When you wrote:
container.querySelector('#click')
This will return a reference to the button, I name it button:
var button = container.querySelector('#click');
Then the call button.addEventListener(...) is basically an analogy to Subject.addObserver(...). This means that your button object is actually the Subject in OP. The call Subject.notify(...) is implicitly handled by the JavaScript engine. And your inline function to consume the click event is actually the Observer.
The main difference between your code and the code of jarrettmeyer.com lies in the question: who is the Subject? In jarrettmeyer.com, Subject is not any button but a separated object: the Counter object. This offers some advantages:
The Subject can associate with many buttons, for example, jarrettmeyer can write: $("#anotherButton").on("click", function () { counter.increment(); });
The Subject can easily maintain whatever state and notify whatever info to the Observer. In jarrettmeyer's example, these state/info are simply a count number. Indeed, in your example, no state/info of the button (except the fact that it has just been clicked) is notified since the count number is maintained in your span which belongs to the implementation detail of your Observer and thus not related to OP.
Do you know the code you wrote is also an implementation of the observer pattern? The function you passed after the 'click' argument is an observer and it is added to the observers' array. You can add as many functions as you want against the 'click' event of the same element. They all will be fired by running a loop in the observers' array when the 'click' event happens.
If you have only one action happening as a response to some other action, you can write the action manually without implementing the observer pattern. However, when you want to do multiple things at multiple parts of the codebase in response to some event, observer pattern is the way to go.
Yes, you are right. addEventListener or jQuery .on() could do the similar thing as Observer. They are good enough for most of the front-end usage. But in the following use cases (backend/abstraction), observer pattern is better:
The event being listened is not related to the DOM elements (e.g. JS object's mutation)
You would like to have a better control on removeEventListener (e.g. multiple anonymous callback functions bound on an event type, you would like to move one of them)
The .notify method in the example is made to loop all the callback function in registry array, and try to execute all of them.
Here's a Codepen to show how observer help in the real world.
And here's a simple observer implementation when I learn Observer pattern:
var App = function() {
// This array will store all the subscribers.
this.subscribers = [];
}
// Subscribe, unsubscribe and publish are three base methods in this pattern
App.prototype.subscribe = function(subscriber) {
this.subscribers.push(subscriber);
}
App.prototype.unsubscribe = function(subscriber) {
for (var i = 0; i < this.subscribers.length; i++) {
if (this.subscribers[i] === subscriber) {
this.subscribers.splice(i, 1);
}
}
}
App.prototype.publish = function(message) {
for (var i = 0; i < this.subscribers.length; i++) {
console.log(this.subscribers[i] + ' got ' + message + '!');
}
}
// Testing code.
var myApp = new App();
myApp.subscribe('Timmy');
myApp.subscribe('Tommy');
myApp.publish('a new magazine'); // Both Timmy & Tommy got the new magazine
myApp.unsubscribe('Timmy');
myApp.publish('a new book'); // Now only Tommy got the new book
Attached the Codepen for reference.
So, basically all my events(there's min. 360 of them) have team1 vs. team2 or - vs. team2 or team1 vs. - placeholders.
And on the initial render events change color depending on whether the event has one or two teams.
Orange color for the one team , and green for the two teams. Also, the event changes color on click.
But mostly, I'm interested in increasing performance with rendering events.
Rendering performance is going really bad in fullCalendar, and I couldn't find any solution to this problem.
So here's my code:
eventRender: function (event, element) {
$(element).append((event.teams[0] != null ? event.teams[0] : '-') + '</br> vs. </br>' + (event.teams[1] != null ? event.teams[1] : '-'));
if (event.teams.length === 1) {
$(element).css('background', 'orange');
}
else if (event.teams.length > 1) {
$(element).css('background', 'green');
}
}
My main issue is that when I click on event to change its color, the script automatically goes to the eventRender or eventAfterRender event, and its behavior is exactly like the for statement - it iterates over events and then it does the stuff that I want to do with the individual event, but only when the loop lands on the clicked event.
Also, in the eventClick I've called $('#myCalendar').fullcalendar('updateEvent',event) and I think there is a bug, because it automatically goes to the eventAfterRender or the eventRender, iterating over the whole events collection again.
Even tough 'updateEvent' parameter should instruct fullCalendar to update/render only the specific event.
Does anyone have any advice on this subject?
Fullcalendar now supports the renderEvents method: https://fullcalendar.io/docs/renderEvents.
Simply build your events list and send them all at once:
$("#calendar").fullCalendar('renderEvents', events, true);
I know this is an old question, but i solved the same performance problem in v5 of the fullcalendar with this configuration option:
https://fullcalendar.io/docs/rerenderDelay
It basically adds a delay after each operation that would trigger a render event.
if the framework detects another operation within that delay, it renders these events in one operation and thereby increases performance.
setting the value to 1 (so 1 millisecond delay) did the trick for me. I simply added it to the configuration in my angular component:
calendarOptions: CalendarOptions = {
...,
rerenderDelay: 1,
}
In fullcalendars source-code (at least in my version of it) there is the renderEvent-handler, that calls reportEvents -function which is the bottleneck of performance. I worked my way around this issue, by adding handling of mass-rendering events to the source-code.
I wrote a short function:
function massRenderEvents(events, stick) {
var i;
for (i = 0; i < events.length; i += 1) {
normalizeEvent(events[i]);
if (!events[i].source) {
if (stick) {
stickySource.events.push(events[i]);
events[i].source = stickySource;
}
cache.push(events[i]);
}
}
reportEvents(cache);
}
Under "EventManager" -function, and added it to EventManagers exports, like:
t.massRenderEvents = massRenderEvents;
Now, for every batch of rendered events, the heavy and slow reportEvents is called just once. Note, that massRenderEvents -function is very similar to the original renderEvent -function.
I have changed
$("#calendar").fullCalendar('renderEvent', eventData1, true);
to
$("#calendar").fullCalendar('addEventSource', eventData1, true);
and that worked for me. I have read the issue on several related website and as per their suggestion I have done this.
The main difference between renderEvent and addEventSource is that the first one tries to interact with calendar when even a single event created which take much time because of regular callback function, and the second one sends a bucket of JSON events to calendar which require only single callback function which improve the performance and take less time.
My requirements
Because of the asynchronous architecture of my applications I am looking for an 'event' system which has the following two two properties:
The events should be able to fire multiple times (possible with events, but not with promises)
When I start listening for an event that has already been fired, I want the listener to fire once immediately (as with promises)
The reason for 1. is that there are a lot of events (e.g. the updating of certain data) that I want to be able to fire multiple times. But I would like to combine this with 2. so that if an event has already fired upon adding the listener, this listener gets called immediately. This is because I'm not always sure (and I don't want to be sure) which piece of code gets run first.
My 'solution'
I have thought up the following solution. I'm using this in an AngularJS application therefore the AngularJS context, but the question is applicable for Javascript in general. Note that I simplified the code.
app.controller('AppCtrl', function(CustomEventEmitter){
// Broadcast an event. No listener added so nothing happens
CustomEventEmitter.broadcast('event');
// Add the event listener. Because the event allready fired, the listener gets called immediatly
CustomEventEmitter.on('event', function(){
console.log('Event emitted');
});
// Broadcast an other event
CustomEventEmitter.broadcast('event');
});
app.service('CustomEventEmitter', function(){
var
listeners = {},
memory = [];
this.broadcast = function(name){
// The normal broadcasting of the event to the listener
if(listeners[name]) {
listeners[name].forEach(function(listener){
listener();
});
}
// Push the event into the 'memory'
memory.push(name);
};
this.on = function(name, listener){
// The normal adding of the listener
if(!listeners[name]) {
listeners[name] = [];
}
listeners[name].push(listener);
// If an event is already in memory, call the listener
if(memory.indexOf(name) !== -1) {
listener();
}
};
});
My questions
My questions are these:
What is the 'best practice' solution for my requirements?
What do you think of my 'solution'?
Am I missing something completely obvious?
The reason for the last question is that it seems to me that this is a very common design paradigm but I seem unable to find the best way to solve this in simple and concise way.
Note
I understand this can be solved with the adding of extra code (e.g. before adding the listener, check in an other way if the event you are going to listen for already happened) but this is not what I'm looking for.
A "property" from bacon.js does exactly what you are asking for. This falls under the broader category of functional reactive programming (FRP). The most popular two libraries for this in JavaScript are probably
bacon.js
Reactive Extensions
Both of which provide the specific tool you're asking for, along with a vast array of alternatives.
So far EVERY ONE of my questions was already asked and answered here, to many thanks to all time machine help I already got here. :) But everything ends one day and so here is my first own question:
For error handling purposes, I'm trying to attach some generic listeners to all interesting components as they are added. This can be done with Ext.ComponentMgr.all.on('add', function (cnt, cmp) {}). Then I look what type of component it is, for example to add a click listener to every button.
What I now need to do is to add an activate listener to every panel which is added to a tabpanel. My problem is: I have no clue how to determine if a component is a direct descendant of a tabpanel and how to do it the right way.
I already tried this:
Ext.ComponentMgr.all.on('add', function (cnt, cmp) {
if (cmp.ownerCt != undefined && cmp.ownerCt.getXType () == 'tabpanel') {
console.log (cmp.getXType () + ' in tabpanel with id "' + cmp.getId ());
}
});
The bad thing is, some Ext doc reads "Do not rely on ownerCt" and it's right: Every other of my components have an ownerCt, but NOT panels when added late to tabpanels.
I know there is Ext.Panel.findParentByType(), but this finds containers at any level above and I don't think it's the right way (-> performance).
Thanks in advance for every answer!
As you said, the ownerCt may get poked in at any time during the construction depending on the order in which you do it. Why not create a tab panel subclass that automatically handles it for you?
I've done some more trial and error and found a solution for myself. I think I should share it with you, maybe someone can use it one day:
When Ext.ComponentMgr.all.on('add', function (cnt, cmp) {}) fires, ownerCt is of course empty, because the component only has been created, but not necessarily added to another component.
So I've done the following:
Ext.ComponentMgr.all.on('add', function (cnt, cmp) {
cmp.addListener ('added', function (cmp) {
var parent = cmp.findParentByType ('tabpanel');
if (parent != undefined) {
if (parent.items.contains (cmp)) {
cmp.addListener ('activate', function (cmp) {
doThings();
});
}
}
});
}
When the component is added to the component manager (new Ext.Panel()), create a listener for added to the component
When added, check if any parent is a tabpanel
If it is, check if you component is a direct descendant of the tabpanel (= is contained in its items)
???
Profit!