1

Using the abcjs library in an Angular 7 application, I have the following template:

<div id="{{device.name}}"></div>

with the controller:

@Component({
  selector: 'midi-sheet',
  templateUrl: './sheet.component.html',
  styleUrls: ['./sheet.component.css']
})
export class SheetComponent implements AfterViewInit, OnDestroy  {

  // devices$: Observable<Array<Device>>;
  @Input() device: Device;

  constructor(
    // private storeService: StoreService,
    private sheetService: SheetService
  ) { }

  ngAfterViewInit() {
    this.createSheet();
  }

  ngOnDestroy() {
  }

  private createSheet() {
    const sheet = this.sheetService.createSheet(this.device.name);
  }

}

and its service:

export class SheetService {

  public createSheet(name: string) {
    const elementName: string = '#' + name;
    // tslint:disable-next-line: max-line-length
    const tune = 'X:1\nT: Cooley\'s\nM: 4/4\nL: 1/8\nR: reel\nK: Emin\nD2|:"Em"EB{c}BA B2 EB|~B2 AB dBAG|"D"FDAD BDAD|FDAD dAFD|\n"Em"EBBA B2 EB|B2 AB defg|"D"afe^c dBAF|1"Em"DEFD E2 D2:|2"Em"DEFD E2 gf||\n|:"Em"eB B2 efge|eB B2 gedB|"D"A2 FA DAFA|A2 FA defg|\n"Em"eB B2 eBgB|eB B2 defg|"D"afe^c dBAF|1"Em"DEFD E2 gf:|2"Em"DEFD E4|]\n';
    const sheet = abcjs.renderAbc(elementName, tune, {});
    console.log('Created a sheet for ' + name);
    return sheet;
  }

}

The console log shows the sheet is being created:

Created a sheet for VMPKOutput

and the Chrome browser console element tab shows:

<midi-sheet _ngcontent-hvr-c2="" _nghost-hvr-c4="" ng-reflect-device="[object Object]"><div _ngcontent-hvr-c4="" id="MidiThroughPort-0"></div></midi-sheet>

But there is no visible sheet in the page.

Stephane
  • 11,836
  • 25
  • 112
  • 175

2 Answers2

2

I don't have a sample using Angular, but here is an example with Vue: https://github.com/paulrosen/vue-abcjs-basic-demo/blob/master/src/components/HelloWorld.vue

It looks very similar to what you're doing.

The first thing I notice is that you shouldn't have the '#' in the element id that you are passing in. Perhaps that's it. So const elementName: string = name;

Here's what I would debug:

1) At the time that abcjs.renderAbc(elementName, tune, {}); is called, is there a visible element with the id elementName?

2) Look at the value of sheet that is returned. It should be an array with one item. There should be reasonable data in it. For instance, sheet[0].lines[0].staff[0].voices[0] should return an array of notes.

Paulie
  • 1,940
  • 3
  • 20
  • 34
  • Yes, this is correct. No need for `#` in the string. https://github.com/paulrosen/abcjs/blob/master/src/api/abc_tunebook.js#L138 – Reactgular Jun 01 '19 at 14:36
  • Yes I had seen your basic Vue example and based my code on it. I thought I had seen a # character in there. I shall be back at the computer tomorrow and try that, as well as the other solution below. – Stephane Jun 01 '19 at 18:42
  • The # I had seen was not in your code, now it hits me, it was my mistake, carrying that from another library. – Stephane Jun 01 '19 at 19:58
1

I've never used this library before, but I took a quick look at the source code and I think you can just pass the native element directly. Which would make it easier to create multiple components on the same page without having to generate unique IDs.

The render function is here:

https://github.com/paulrosen/abcjs/blob/master/src/api/abc_tunebook_svg.js#L160

Tunebook assumes it's a DOM element unless the value is a string:

https://github.com/paulrosen/abcjs/blob/master/src/api/abc_tunebook.js#L137

Use the component's element instead.

@Component({
  selector: 'midi-sheet',
  // drop the template as it's not needed
  styleUrls: ['./sheet.component.css']
})
export class SheetComponent implements OnInit {
  @Input() device: Device;

  constructor(private sheetService: SheetService,
              private el: ElementRef<HTMLElement>) { }

  ngOnInit() {
    // should work inside ngOnInit and pass the DOM element of this component
    this.sheetService.createSheet(this.el);
  }
}

In your service just update to use an element.

export class SheetService {
  public createSheet(el: ElementRef<HTMLElement>) {
    const tune = 'X:1\nT: Cooley\'s\nM: 4/4\nL: 1/8\nR: reel\nK: Emin\nD2|:"Em"EB{c}BA B2 EB|~B2 AB dBAG|"D"FDAD BDAD|FDAD dAFD|\n"Em"EBBA B2 EB|B2 AB defg|"D"afe^c dBAF|1"Em"DEFD E2 D2:|2"Em"DEFD E2 gf||\n|:"Em"eB B2 efge|eB B2 gedB|"D"A2 FA DAFA|A2 FA defg|\n"Em"eB B2 eBgB|eB B2 defg|"D"afe^c dBAF|1"Em"DEFD E2 gf:|2"Em"DEFD E4|]\n';
    return abcjs.renderAbc(el.nativeElement, tune, {});
  }
}
Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • Using the `private el: ElementRef` injected element made the `abc` builder give the error `wrapper.appendChild is not a function at new Svg (svg.js:23)` I will use a string instead as it works out just fine. – Stephane Jun 02 '19 at 16:36