window.requestAnimationFrame() not working in Ionic2 application - html

I'm trying to use an HTML5 canvas for animation in my Ionic 2 app. For this, I have to use window.requestAnimationFrame() to animate my canvas. This is my code:
import { Component, ViewChild, Renderer } from '#angular/core';
import { Platform } from 'ionic-angular';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
...
export class CubePage {
#ViewChild('glCanvas') canvas: any;
canvasElement: any;
ctx: any;
radius: number;
leftBallX: number;
leftBallY: number;
rightBallX: number;
rightBallY: number;
constructor(public navCtrl: NavController, public navParams: NavParams, public renderer: Renderer, public platform: Platform) {
}
ngAfterViewInit() {
console.log(this.canvas);
this.radius = 25;
this.canvasElement = this.canvas.nativeElement;
this.renderer.setElementAttribute(this.canvasElement, 'width', this.platform.width() + "");
this.renderer.setElementAttribute(this.canvasElement, 'height', this.platform.height() + "");
this.ctx = this.canvasElement.getContext('2d');
this.leftBallX = 5;
this.leftBallY = 5;
window.requestAnimationFrame(this.cycle);
}
cycle() {
if (this.leftBallX < this.platform.width() / 2) {
this.ctx.clearRect(0, 0, this.platform.width(), this.platform.height());
this.drawCenterLine();
this.updateLeftBall(this.leftBallX + 5, this.leftBallY);
this.drawLeftBall();
}
window.requestAnimationFrame(this.cycle);
}
...
}
It gives the runtime error Cannot read property 'leftBallX' of undefined when I load the application in my web browser. However, when I eliminate the window.requestAnimationFrame(this.cycle) lines, replacing the first with this.cycle(), there are no errors. Is there a problem with using window in Ionic/Angular?

To solve your problem, first, you need to understand this context when you call a javascript function. Lets see the example:
foo = 0;
ngAfterViewInit(){
let self = this;
//way #1
setTimeout(function(){
console.log(this.foo); //undefinded; because this != self;
},1000);
//way #2
setTimeout(()=>{
console.log(this.foo); //0; because this == self;
},1000)
}
When you call function by way #1, javascript re-bind this object, so you can not find foo propertive in this.
When you call function by way #2 (arrow function), javascript does not re-bind this object, so you can use this as expected.
Now you can solve your problem by using arrow function:
requestAnimationFrame(()=>{this.cycle()})
See more about arrow function

Related

Generating unattached dynamic components in Angular

I went through this issue while working on the ScheduleJS framework. At some point I am provided with a HTMLCanvasElement which I want to replace with a dynamically generated component programatically. To do so, and to keep the code as clean as possible, I'd like to create my own Angular components at runtime and use the HTMLCanvasElement.replaceWith(component) method from the provided HTMLCanvasElement replacing the canvas with the dynamically created component.
Here is the Angular service I came up with, which does the job the way I expected:
import {ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable, Injector, Type} from "#angular/core";
import {ReplacementComponent} from "xxx"; // This is a higher order type of Component
#Injectable({providedIn: "root"})
export class DynamicComponentGenerator {
// Attributes
private _components: Map<string, ComponentRef<ReplacementComponent>> = new Map();
private _currentKey: number = 0;
// Constructor
constructor(private _appRef: ApplicationRef,
private _resolver: ComponentFactoryResolver,
private _injector: Injector) { }
// Methods
create(componentType: Type<ReplacementComponent>): ComponentRef<ReplacementComponent> {
const componentRef = componentType instanceof ComponentRef
? componentType
: this._resolver.resolveComponentFactory(componentType).create(this._injector);
this._appRef.attachView(componentRef.hostView);
this._components.set(`${this._currentKey}`, componentRef);
componentRef.instance.key = `${this._currentKey}`;
this._currentKey += 1;
return componentRef;
}
remove(componentKey: string): void {
const componentRef = this._components.get(componentKey);
if (componentRef) {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(componentKey);
}
}
clear(): void {
this._components.forEach((componentRef, key) => {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
this._components.delete(key);
});
this._currentKey = 0;
}
}
So basically this service lets me create a component with .create(ComponentClass) remove it by providing the component key .remove(key) and clear() to remove all the components.
My issues are the following:
The ComponentFactoryResolver class is deprecated, should I use it anyways?
Could not manage to use the newer API to create unattached components (not able to have access to an Angular hostView)
Is there a better way to do this?
Thank you for reading me.
You could try using new createComponent function:
import { createComponent, ... } from "#angular/core";
const componentRef =
createComponent(componentType, { environmentInjector: this._appRef.injector});
this._appRef.attachView(componentRef.hostView);

Directive not responding due to same mouse events being used in multiple directives on same element

I have a feature where box can be dragged and dropped into the grey area(Please refer to the stackblitz link)
Once the box is dragged and dropped , the box can only be moved within the grey area by clicking on the pink color of the box.
Resizing functionality has also been added, so the box can be resized.
Before resize directive was added the box can only be moved within grey area but after adding adding the resize directive when we resize, the box starts moving out of the grey area , the issue is the box should not move out of the grey area when resizing is done
Stackblitz link
https://stackblitz.com/edit/angular-rgeq2p?file=src/app/hello.component.html
hello.component.html [ has the box and directives applied on the box ]
<div appMovableArea appDropzone (drop)="move(currentBox, dropzone1)">
<div *ngFor="let box of dropzone1"
appDroppable
(dragStart)="currentBox = box"
appMovable
resize>
{{ box.dis }}
</div>
</div>
where the box has (dragStart) output event emitter which is bound to draggable directive([appDroppable + draggable] and for the
move functionality appMovable, appMovableArea directives are there) .And the events are shared among directives using Droppable.service.ts
The (drop) is an output event emitter applied on grey area using
dropzone[appDropzone] directive
import { Directive, ElementRef, EventEmitter, HostBinding, HostListener,
OnInit, Output, SkipSelf } from '#angular/core';
import { DroppableService } from './droppable.service';
#Directive({
selector: '[appDropzone]',
providers: [DroppableService]
})
export class DropzoneDirective implements OnInit {
#Output() drop = new EventEmitter<PointerEvent>();
#Output() remove = new EventEmitter<PointerEvent>();
private clientRect: ClientRect;
constructor(#SkipSelf() private allDroppableService: DroppableService,
private innerDroppableService: DroppableService,
private element: ElementRef) { }
ngOnInit(): void {
this.allDroppableService.dragStart$.subscribe(() =>
this.onDragStart());
this.allDroppableService.dragEnd$.subscribe(event =>
this.onDragEnd(event));
this.allDroppableService.dragMove$.subscribe(event => {
if (this.isEventInside(event)) {
this.onPointerEnter();
} else {
this.onPointerLeave();
}
});
this.innerDroppableService.dragStart$.subscribe(() =>
this.onInnerDragStart());
this.innerDroppableService.dragEnd$.subscribe(event =>
this.onInnerDragEnd(event));
}
private onPointerEnter(): void {
if (!this.activated) {
return;
}
this.entered = true;
}
private onPointerLeave(): void {
if (!this.activated) {
return;
}
this.entered = false;
}
private onDragStart(): void {
this.clientRect = this.element.nativeElement.getBoundingClientRect();
this.activated = true;
}
private onDragEnd(event: PointerEvent): void {
if (!this.activated) {
return;
}
if (this.entered) {
this.drop.emit(event);
}
}
private onInnerDragStart() {
this.activated = true;
this.entered = true;
}
private onInnerDragEnd(event: PointerEvent) {
if (!this.entered) {
this.remove.emit(event);
}
}
private isEventInside(event: PointerEvent) {
return event.clientX >= this.clientRect.left &&
event.clientX <= this.clientRect.right &&
event.clientY >= this.clientRect.top &&
event.clientY <= this.clientRect.bottom;
}
}
Then on the box (dragStart) output event emitter which is present indraggable directive[appDraggable] which listens for pointerdown events
import { Directive, EventEmitter, HostBinding, HostListener, Output,
ElementRef } from '#angular/core';
#Directive({
selector: '[appDraggable],[appDroppable]'
})
export class DraggableDirective {
#Output() dragStart = new EventEmitter<PointerEvent>();
#Output() dragMove = new EventEmitter<PointerEvent>();
#Output() dragEnd = new EventEmitter<PointerEvent>();
constructor(public element: ElementRef) {}
#HostListener('pointerdown', ['$event'])
onPointerDown(event: PointerEvent): void {
if (event.button !== 0) {
return;
}
this.pointerId = event.pointerId;
this.dragging = true;
this.dragStart.emit(event);
}
#HostListener('document:pointermove', ['$event'])
onPointerMove(event: PointerEvent): void {
if (!this.dragging || event.pointerId !== this.pointerId) {
return;
}
this.dragMove.emit(event);
}
#HostListener('document:pointercancel', ['$event'])
#HostListener('document:pointerup', ['$event'])
onPointerUp(event: PointerEvent): void {
if (!this.dragging || event.pointerId !== this.pointerId) {
return;
}
this.dragging = false;
this.dragEnd.emit(event);
}
}
Movable directive for maintaining move inside the grey area which in
turn uses calculation based from movable-area directive
import { Directive, ElementRef, HostBinding, HostListener, Input } from
'#angular/core';
import { DraggableDirective } from './draggable.directive';
import { DomSanitizer, SafeStyle } from '#angular/platform-browser';
interface Position {
x: number;
y: number;
}
#Directive({
selector: '[appMovable]'
})
export class MovableDirective extends DraggableDirective {
#HostBinding('style.transform') get transform(): SafeStyle {
return this.sanitizer.bypassSecurityTrustStyle(
`translateX(${this.position.x}px) translateY(${this.position.y}px)`
);
}
#HostBinding('class.movable') movable = true;
position: Position = {x: 0, y: 0};
private startPosition: Position;
#Input('appMovableReset') reset = false;
constructor(private sanitizer: DomSanitizer, public element: ElementRef) {
super(element);
}
#HostListener('dragStart', ['$event'])
onDragStart(event: PointerEvent) {
this.startPosition = {
x: event.clientX - this.position.x,
y: event.clientY - this.position.y
}
}
#HostListener('dragMove', ['$event'])
onDragMove(event: PointerEvent) {
this.position.x = event.clientX - this.startPosition.x;
this.position.y = event.clientY - this.startPosition.y;
}
#HostListener('dragEnd', ['$event'])
onDragEnd(event: PointerEvent) {
if (this.reset) {
this.position = {x: 0, y: 0};
}
}
}
The resize directive is also present in the stackblitz link.
And styles for resize directive are present in styles.css
Here is implementation of Drag&Drop directive, sorry can't fix your code because it's a mess same as resize:
Directive
#Directive({
selector: '[draggable]'
})
class Draggable implements onInit {
private element: HTMLElement;
private handlerNode: HTMLElement;
private data: {x: number, y: number};
#Input('draggable')
private handler: string;
#HostListener('mousedown', ['$event'])
mousedown(e) {
if (e.target === this.handlerNode) {
var rect = this.element.getBoundingClientRect();
this.data = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
} else {
delete this.data;
}
}
#HostListener('document:mouseup', ['$event'])
mouseup(e) {
delete this.data;
}
constructor(#Inject(ElementRef) element: ElementRef) {
this.element = element.nativeElement;
}
ngOnInit() {
this.element.classList.add('dragabble');
this.handlerNode = this.element.querySelector(this.handler);
}
#HostListener('document:mousemove', ['$event'])
onPointerMove(e: PointerEvent): void {
if (this.data) {
var x = e.clientX - this.data.x;
var y = e.clientY - this.data.y;
this.element.style.left = x + 'px';
this.element.style.top = y + 'px';
}
}
}
CSS
.dragabble .handler {
position: absolute;
width: calc(100% - 12px);
height: calc(100% - 12px);
left: 6px;
top: 6px;
}
Template:
<div [resize]="toggle" style="left: 100px; top: 50px"
[draggable]="'.handler'">
<div class="handler">xxx</div>
</div>
Demo: https://codepen.io/jcubic/pen/wvwJNqQ?editors=0110
One small hack you can do is add 'resizing' class to the element in resize directive and check whether that class is present in draggable directive if present do not make it draggable. I can't get your code because it's so difficult. I don't know why you made this so complex.

Ionic native Google Maps not showing correctly

few weeks ago I had a problem with the Google maps ionic native module, and I made a question (not solved).
Now I'm testing in a blank page and the map is shown, but it looks like:
This is my xml file, where I have the div that will contain the Map.
<ion-header>
<ion-navbar>
<ion-title>maptest</ion-title>
</ion-navbar>
</ion-header>
<ion-content style="background: pink;">
<div #map id="map" style="height: 80%;"></div>
</ion-content>
And here we have the ts file. Here I create the Map using ViewChild
import { Component, ViewChild, ElementRef } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import {
GoogleMaps,
GoogleMap,
CameraPosition,
LatLng,
GoogleMapsEvent,
GoogleMapOptions
} from '#ionic-native/google-maps';
/**
* Generated class for the MaptestPage page.
*
* See http://ionicframework.com/docs/components/#navigation for more info
* on Ionic pages and navigation.
*/
#IonicPage()
#Component({
selector: 'page-maptest',
templateUrl: 'maptest.html',
})
export class MaptestPage {
#ViewChild('map') mapElement: ElementRef;
map: GoogleMap;
constructor(public navCtrl: NavController, public navParams: NavParams, private _googleMaps: GoogleMaps) {
}
ngAfterViewInit() {
console.log('ngAfterViewInit');
this.initMap();
}
initMap() {
let element = this.mapElement.nativeElement;
this.map = GoogleMaps.create(element, {});//this._googleMaps.create(element);
// Wait the MAP_READY before using any methods.
this.map.one(GoogleMapsEvent.MAP_READY).then(() => {
console.log('Map is ready!');
}).catch( err =>{
console.error("Error maperino --> "+err);
});
}
moveCamera(location: LatLng) {
let options = {
target: location,
zoom: 18,
tilt: 30
}
this.map.moveCamera(options);
}
}
I don't know what I'm doing wrong :(
Since you can see the google logo, your code is fine.
The problem is your api key.
You need to try to regenerate the API key, and reinstall it.
Make sure you enable the google maps android api v2 and the google maps sdk for ios before generating the api keys.
https://github.com/mapsplugin/cordova-plugin-googlemaps-doc/blob/master/v1.4.0/TroubleShooting/Blank-Map/README.md
Even you tried the steps, and but you still get the gray map, contact to me directly. I will check it.
Its an answer, because I don't have reputation to add a comment.
Try add the Platform provider and then use platform ready inside the constructor to initialize your map.
contructor(public navCtrl: NavController,
public navParams: NavParams,
private _googleMaps: GoogleMaps,
private _platform: Platform){
this.platform.ready().then(() => {
this.initMap();
});
}
EDIT: It may be zoom too, try zoom out
EDIT2: Take a look at this slide about ionic and google-maps native
https://docs.google.com/presentation/d/1zlkmoSY4AzDJc_P4IqWLnzct41IqHyzGkLeyhlAxMDE/edit#slide=id.g292c767148_0_47

Ionic Native Google Maps plugin - set app background color

I'm having trouble with Ionic 3, specifically setting the app background color when using the native Google Maps plugin. The map is a native element "under" the browser, and having background color set for the Ionic app covers the map and prevents it from showing.
When looking at the docs, there is a setBackgroundColor(color) method in the Environment class, but I have no idea where I'm supposed to use that class. Does anyone know?
Ok, I simply instantiated the Environment class and then called setBackgroundColor. Like this:
import { Environment } from '#ionic-native/google-maps`;
export class MyClass {
environment: Environment = null;
myMethod(): void {
this.environment = new Environment();
this.environment.setBackgroundColor("red");
}
}
Wow. It's kind of surprised for me. #mkkekkonen's answer worked certainly, but it is not my intended design way.
(I'm the author of cordova-plugin-googlemaps, and main maintainer of #ionic-native/google-maps plugin).
The Environment, Geocoder, Spherical, Poly(oh, not implemented yet), and Encoding classes are static class in original plugin.
For example, I think nobody do like this, but it is possible.
This is not my intended way.
import { Environment } from '#ionic-native/google-maps`;
export class MyClass {
environment: Environment = null;
myMethod1(): void {
(new Environment()).setBackgroundColor("red");
}
myMethod2(): void {
(new Environment()).setBackgroundColor("blue");
}
}
The correct way is to use provider (#4.3.3)
#IonicPage()
#Component({
selector: 'page-set-background-color',
templateUrl: 'set-background-color.html',
providers: [
Environment
]
})
export class SetBackgroundColorPage {
map: GoogleMap;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public environment: Environment,
public googleMaps: GoogleMaps) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad SetBackgroundColorPage');
this.map = this.googleMaps.create('map_canvas');
this.environment.setBackgroundColor('red');
}
}
Please use this way, mkkekkonen.
I will fix this bug in the next release.
update
In the next release after #ionic-native#4.3.4, the code would be like this:
#IonicPage()
#Component({
selector: 'page-set-background-color',
templateUrl: 'set-background-color.html'
})
export class SetBackgroundColorPage {
map: GoogleMap;
constructor() {
}
ionViewDidLoad() {
console.log('ionViewDidLoad SetBackgroundColorPage');
this.map = GoogleMaps.create('map_canvas');
Environment.setBackgroundColor('red');
}
}

Type 'any' is not a constructor function type. class google.maps.OverlayView

What I want to implement is a piechart marker like this link I cant figure out how to draw the overlay.I saw a similiar question here, but none of these answers work for me.
I am getting this error - "Type 'any' is not a constructor function type.
class google.maps.OverlayView"
import { Component, Input, Output, EventEmitter } from '#angular/core';
const chartsApi = 'https://www.google.com/jsapi';
declare var google: any;
declare var $: any;
import { } from '#types/googlemaps'
#Component({
selector: 'chart-marker',
templateUrl: './chart-marker.component.html',
styleUrls: ['./chart-marker.styles.scss']
})
export class ChartMarkerComponent {
#Input() options: Object;
#Input() data: Object;
#Output() chartReady: EventEmitter<boolean> = new EventEmitter();
private divToDraw: any;
private innerDiv: any;
private chart: any;
constructor() {
this.loadCharts();
}
// the code below is giving me this error
USGSOverlay = class extends google.maps.OverlayView {
bounds_: any;
image_: any;
map_: any;
div_: any;
constructor(bounds, image, private map) {
super();
// Initialize all properties.
}
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
onAdd() {
};
draw() {
};
// The onRemove() method will be called automatically from the API if
// we ever set the overlay's map property to 'null'.
onRemove() {
};
};
private loadCharts() {
if (typeof google === "undefined" || typeof google.charts === "undefined") {
let node = document.createElement('script');
node.src = chartsApi;
node.type = 'text/javascript';
document.getElementsByTagName('body')[0].appendChild(node);
node.onload = () => {
google.load("visualization", "1", { packages: ['corechart'], "callback": this.drawChart.bind(this) });
}
}
}
private drawChart() {
this.chartReady.emit(true);
}
}
I suspect that TypeScript doesn't recognize your Google Maps API typings so it assumes what you are referring to is of type any which it doesn't allow to inherit from, hence the error message. As far as I know you can't import the typings but they are treated as ambient declarations in TS lingo - so having a tsconfig.json and the typings installed in your project directory should suffice.
I would recommend doing a minimal example without your Angular setup in place to see if TypeScript actually uses the provided typings, looking at the actual typings the class should be inheritable und usable in the way you're trying to do it.
Something like this in a single file should suffice to see whether TS is picking up your typings:
class extends google.maps.OverlayView {
constructor() {
super();
}
};