I am using an external library on my app, I noticed that when I change the route then return back the library stop working.
The libraries are imported like this:
import videojs from "video.js"
import "videojs-markers"
....
ngOnInit() {
this.video = videojs('demo');
this.video.on('pause', function () {
this.onPauseVideo(this.video.currentTime())
}.bind(this));
this.video.on('play', function () {
this.onPlayVideo(this.video.currentTime())
}.bind(this));
this.setUpMarkers(this.video);
this.subscription = this.commentService.commentsChange.subscribe((newComments:VideoComment[])=>{
this.video.markers.add(newComments.map(el=>{
return {time:el.time,text:el.text,overlayText:el.overlayText}})
);
})
}
....
any suggestions.
remove .bind(this) from your code. arrow functional should help
you.
This line move to constructor(){}
this.video = videojs('demo');
remove all event listeners like this ngOnDestroy(){ this.video.off('pause')
Related
I am trying to organise my code into components/modules and I'm struggling to understand how to deal with event listeners and/or communicate between JS files. I will explain with a simplified example:
In this hypothetical, app.js imports all the other js file components. In app.js I call a function in nav.js to render my nav to the DOM and I add event listeners to a single button.
When the button is clicked my event handler inside nav.js picks it up and I want to somehow communicate back to app.js and tell it to call a function inside pageA.js to render the page.
I'm not sure if this is the right approach or if I'm missing a key concept to get this working. Any tips would be appreciated.
index.js
import Nav from './nav.mjs';
import PageA from './pageA.mjs';
export default class App {
constructor() {
this.nav = new Nav();
this.pageA = new PageA();
this.nav.renderNav();
}
}
nav.js
export default class Nav {
constructor() {
}
renderNav(){
let button = document.createElement("button");
button.innerHTML = "Page A";
document.body.appendChild(button);
button.addEventListener ("click", function() {
// How do I call renderPageA() in pageA.js?
});
}
}
pageA.js
export default class PageA {
constructor() {
}
renderPageA(){
let pageDiv = document.createElement('div');
document.body.appendChild(pageDiv);
}
}
You can pass pageA instance as a parameter in the Navs constructor.
//Nav constructor
constructor(pageA){
this.pageA = pageA
}
//Add this to the event listener's callback:
this.pageA.renderPageA()
//index.js
this.nav = new Nav(pageA)
Note that you have to instantiate pageA before nav for this to work.
Under my Angular App, I'm using some 3rd library widget which is rendering in my component.
My template is:
<div>
<myWidGet></myWidGet>
</div>
Inside myWidGet there some button element that I want handle their events.
The button have those classes : .dx-edit-row .dx-command-edit .dx-link-save
so i i do that :
export class myClass AfterViewInit {
constructor(private elRef: ElementRef){}
ngAfterViewInit() {
this.elRef.nativeElement.querySelector('.dx-edit-row .dx-command-edit .dx-
link-save').on('click', (e) => {
alert('test');
});
}
}
My purpose is to get reference to my button and handle the click event from it.
Suggestions?
Normally the 3rd party widget should provide a click handler like so:
<myWidGet (click)="myFunction($event)"></myWidGet>
and in the controller:
myFunction(evt) {
const target = evt.target
console.log('test')
}
However, if they do not expose click handlers then I would seriously consider not using the widget.
If want to use the widget anyway then do this using jQuery:
ngAfterViewInit() {
$('.dx-edit-row.dx-command-edit.dx-link-save').on('click', (evt) => {
const target = evt.target
console.log('test')
});
}
The above assumes ALL these classes are present on the same button.
Or just use vanilla JS.
If the buttons are not available on ngAfterViewInit() then you could do this:
ngAfterViewInit() {
const interval = setInterval(() => {
const button = $('.dx-edit-row.dx-command-edit.dx-link-save')
// if button is ready
if (button) {
// add click handlers
button.on('click', (evt) => {
const target = evt.target
console.log('test')
});
// stop polling
clearInterval(interval)
}
}, 100)
}
Accessing DOM elements using jQuery is not really a good practice. Use ElementRef with Renderer2 instead. Also, there's nothing like ngOnViewInit in Angular. It's ngAfterViewInit.
Once the View loads, inside the ngAfterViewInit, you can get access to the HTMLElement using the nativeElement on ElementRef instance. You should explicitly typecast it into HTMLElement so as to get intellisence.
You can then call querySelector on it and pass it the classes. This will give you the button element.
Now you use Renderer2's instances' listen method. This takes three args:
The element you want to listen to events on(btnElement).
The Name of the event(click).
The callback function.
This would translate to code like:
constructor(
private el: ElementRef,
private renderer: Renderer2
) {}
ngAfterViewInit() {
const btnElement = (<HTMLElement>this.el.nativeElement)
.querySelector('.dx-edit-row.dx-command-edit.dx-link-save');
this.renderer.listen(btnElement, 'click', () => {
alert('Buton was clicked');
});
}
Here's a Working StackBlitz for your ref.
You can customize the third party widget using Angular Directive. It will allow to access DOM element and attach listeners using renderer2.
<myWidGet customEvents></myWidGet>
Directive:
#Directive({
selector: '[customEvents]'
})
export class CustomEventsDirective implements OnInit {
private element: any;
constructor(private el: ElementRef, private renderer: Renderer2) {
this.element = el.nativeElement;
}
ngOnInit() {
const btnElement = this.element.querySelector('.dx-edit-row.dx-command-edit.dx-link-save');
this.renderer.listen(btnElement, 'click', () => {
alert('Buton was clicked');
});
}
}
So I have the following simple ES6 plugin that can be used to change the background of an element (created for demo purpose) :
src/index.js
class changeBGColor {
constructor({ targetBG , triggerEl }) {
this.changeBGElem = document.getElementById(targetBG);
this.triggerElem = document.getElementById(triggerEl);
this.addEventListeners();
//this.onClick = this.onClick.bind(this);
//this.onClick.bind(this);
}
addEventListeners() {
this.triggerElem.addEventListener( 'click' , this.onClick.bind(this) );
}
changeBG() {
const bgChangeBgElem = this.changeBGElem;
bgChangeBgElem.classList.toggle('myclassName');
}
onClick(ev) {
console.log(this);
if( ev.target.hasAttribute('data-changeBG') ) {
this.changeBG();
}
}
}
export default changeBGColor;
And I am using my plugin in my main.js file like so:
src/main.js
import changeBGColor from './index.js';
new changeBGColor({
targetBG : 'changeBGOne',
triggerEl : 'triggerBGOne'
});
new changeBGColor({
targetBG : 'changeBGTwo',
triggerEl : 'triggerBGTwo'
});
Now the problem i have is inside addEventListeners method, I have the following code:
this.triggerElem.addEventListener( 'click' , this.onClick.bind(this) );
As you can see I use bind() to bind the scope of this to the class changeBGColor , but I don't want to do this in this manner.
I want something like:
this.onClick = this.onClick.bind(this); // i don't really know what this does, saw it in another plugin, but somehow , this does't work for me.
The problem is the above doesn't work either. I copied it above solution from a GitHub repository, so my question is why does the above solution not work for me and more importantly what is it really doing because this.onClick = this.onClick.bind(this); doesn't make sense to me at all, can somebody break this down for me, please.
P.S. I don't want to use arrow functions to circumvent this problem, that works fine, I know.
Here is a working example of the plugin: JSfiddle.
I am trying to implement transitions between pages by using iron:router. I defined the animations in the css and now everything I need is to call them with the iron:router. For some reason the following code:
animateContentOut = function() {
$('#content').removeClass("animated fadeIn");
return $('footer').addClass("hide");
}
fadeContentIn = function() {
$('#content').addClass("animated fadeIn");
return $('footer').removeClass("hide");
}
Router.onBeforeAction(animateContentOut);
Router.onAfterAction(fadeContentIn);
returns an exception:
Route dispatch never rendered. Did you forget to call this.next() in
an onBeforeAction?
As specified in the Iron-Router documentation, now both onBeforeAction and onAfterAction callbacks require this.next(). https://github.com/iron-meteor/iron-router
So simply simply add that line to the end of your fadeContentIn and animateContentOut code.
If you have login try like this
Router.onBeforeAction(function () {
if (!Meteor.user()) {
this.render('Login');
} else {
this.next();
}
});
We're working on a web apps. To keep components pre-configured, I did a lot Ext.extend work.
Here is the pre-definitions.
In the entrance, in index.js
Ext.setup({
onReady: function() {
var app = new App();
}
});
The main class, in App.js, App will new some card components, such as:
App = Ext.extend(Ext.Panel,{
initComponent: function(){
this.sub1 = new Sub_Class_1();
this.sub2 = new Sub_Class_2();
this.sub3 = new Sub_Class_3();
this.items = [this.sub1, this.sub2, this.sub3];
App.superclass.initComponent.call(this);
this.sub1.on('Event1', this.Event1_fn, this)
},
Event1_fn: function() {
//some codes
}
});
The problem comes, in the definition of Sub_Class_1, in Sub_Class_1.js:
var firefun = function() {
this.fireEvent('Event1');
};
Sub_Class_1 = Ext.extend(Ext.Panel,{
//some similar init configure jobs.
//add *firefun* into this class
})
The problem is, if I fire the event in firefun, there is no reaction from the program. Is there something wrong with the scope? Can the community help me with suggestions that I can use to fix it?
I solved it. This is indeed due to the scope problem.
To solve it, I pass the eventName, eventListener, scope to the function. Let it can fire within the correct scope. Boom, it works!
Any better solution?