Directive or provider?

(newbie question)

I am trying to make a custom component (MyCanvas) which draws a circle on a canvas.
On the Home page I want to provide a button which moves the circle.

Using a directive I can get the home page to draw the canvas, but the button doesn’t work.
On the other hand, if I use a provider (dependency injection) the button does something but the canvas is not shown.

MyCanvas.ts:

import {Component, Injectable, Input, ViewChild, ElementRef} from 'angular2/core';

@Injectable()
@Component({
    selector: 'my-canvas',
    template: `<canvas #theCanvas [attr.width]='width' [attr.height]='height'></canvas>`,
})
export class MyCanvas {
    @ViewChild("theCanvas") theCanvas: ElementRef; 

    constructor() {
      this.width = 200;
      this.height = 100;
      this.x = 20;
    }

    ngAfterViewInit() { 
      console.log("ngAfterViewInit");
      this.context = this.theCanvas.nativeElement.getContext("2d");
      this.redraw();
    }

    redraw(): void {
      let context = this.context;

      this.context.strokeStyle = 'black';
      this.context.strokeRect(0, 0, this.width, this.height);
      this.context.beginPath();
      this.context.arc(this.x, 50, 5, 0, 2*Math.PI);
      this.context.stroke();
    }

    move() : void {
      this.x += 10;
      this.redraw();
    }

    private context: CanvasRenderingContext2D;
    private width: number;
    private height: number;
    private x: number;
}

home.html:

<ion-navbar *navbar>
  <ion-title>
    Home
  </ion-title>
</ion-navbar>

<ion-content class="home">
  <ion-card>
    <ion-card-header>
      Moving circle
    </ion-card-header>
    <ion-card-content>
      <my-canvas></my-canvas>
      <button (click)="move()">Move</button>
    </ion-card-content>
  </ion-card>
</ion-content>

Scenario 1 using directives

home.ts:

import {Page} from 'ionic-angular';
import {MyCanvas} from '../../MyCanvas';

@Page({
  templateUrl: 'build/pages/home/home.html',
  directives: [ MyCanvas ],      // <------------
  //providers: [ MyCanvas ]   
})
export class HomePage {
  move(): void {
    this.myCanvas.move();
  }

  myCanvas: MyCanvas;
}

result:

  • canvas is drawn (ngAfterViewInit() is called)
  • button click -> in move() this.myCanvas is undefined

Scenario 2 using providers

home.ts:

import {Page} from 'ionic-angular';
import {MyCanvas} from '../../MyCanvas';

@Page({
  templateUrl: 'build/pages/home/home.html',
  //directives: [ MyCanvas ],
  providers: [ MyCanvas ]     // <------------
})
export class HomePage {
  constructor( myCanvas: MyCanvas) {    // <------------
    this.myCanvas = myCanvas;
  }

  move(): void {
    this.myCanvas.move();
  }

  myCanvas: MyCanvas;
}

result:

  • canvas is not drawn (ngAfterViewInit() is not called)
  • button click -> in move() this.myCanvas is defined, but redraw() causes error because this.context is not defined

I also tried explicitly constructing a MyCanvas, but that results in two objects being constructed.

I guess I’m missing something fundamental here…

Is there a reason MyCanvas is not a Component?

It has a Component attribute, isn’t that enough?
(and also Injectable, I’m not sure whether it makes sense to have two attribute)

Sorry, I missed the fact that it had a Component attribute. How about decorating the myCanvas instance variable in HomePage with @ViewChild(MyCanvas)?

1 Like

That solved the problem indeed!
Thank you!

Alas I run into a new issue, namely that the previous circle is not erased when the canvas is redrawn (after clicking on the button).
When I add context.clearRect(0, 0, this._width, this._height) in redraw() then it always draws two circles, as if it remembers the last state. Very strange…

@Wijnand IMHO the correct question should be “Directive or component?” (because providers are usually used for a service-logic that shouldn’t be tightly-coupled with the UI) and there’s a good answer here:

I would also recommend you to read the following article: The Core Concepts of Angular 2.

Maybe you should check the Canvas API docs to make sure that you’re using it properly.

1 Like

Thank you for the pointers.

The double circle was a stupid mistake (forgot context.BeginPath()).

The initial problem was solved by adding @ViewChild(MyCanvas)

btw: i would avoid decorators like ViewChild. It is better to understand, if you solve such things over @inputs.

so you could add a @Input to your directive/component which draws the circle. Now the parent component can bind a value to this input.

Or your directive provides an additional service, which exports functions to manipulate the directive instance. But that will get confusing, too. if you are using multiple instances and so on

Hey I implemented this already using fabric js. You can check it out at https://market.ionic.io/plugins/ionic-canvas