2

I have been working on a custom component within Angular 2 as I attempt to learn the ropes and make the switch while staying with ES6/ES7 due to job constraints. Say I have a component defined such as this:

// Import Inject, Component and View constructor (for metadata)
import {Inject, Injectable} from 'angular2/core';
import {Component, View} from 'angular2/core';
// Import NgClass directive
import {NgClass} from 'angular2/common';

import { InjectMetadata } from 'angular2/core';

// # Accordion Component

@Component({
  selector: 'accordion, [accordion]',

  // Modify the `host` element with a css class designator
  host: {
    'class': 'panel-group'
  }
})

// Define the view of our `Component` using one or more
// `View` annotations
@View({

  // Link to our external template file
  templateUrl: './components/accordion/accordion.html'
})

// Create and export `Component` class
export class Accordion {

  constructor() {

    this.groups = [];
  }

  // Function to register groups
  addGroup(group) {
    this.groups.push(group);
  }

  closeOthers(openGroup) {
    this.groups.forEach((group) => {
      if(group !== openGroup) {
        group.isOpen = false;
      }
    });
  }

  removeGroup(group) {
    let index = this.groups.indexOf(group);

    if(index !== -1) {
      this.groups.splice(index, 1);
    }
  }
}

I need to pass this into another component called AccordionGroup but when I follow the answers in this Stack Overflow Thread and try to inject like I do with the constructor:

// # AccordionGroup Component

// Annotate AccordionGroup class with `Component`
@Component({
  selector: 'accordion-group, [accordion-group]',
  inputs: ['heading', 'isOpen'],

  // Let Angular know about `Accordion`
  providers: [Accordion]
})

// Define the view of our `Component` using one or more
// `View` annotations
@View({

  // Link to our external template file
  templateUrl: './components/accordion/accordion-group.html',

  // Specify which directives our `Component` will utilize with
  // the `directive` property of the `View` annotation
  directives: [NgClass]
})

// Create and export `Component` class
export class AccordionGroup {

  constructor(accordion) {

    this.isOpen = false;

    this.accordion = accordion;

    this.accordion.addGroup(this);
  }

  // Angular 2 DI desugar'd
  // Reference: https://stackoverflow.com/questions/33026015/how-to-inject-angular2-http-service-into-es6-7-class
  static get parameters() {
    return [[Accordion]];
  }

  toggleOpen(event) {
    event.preventDefault();
    this.isOpen = !this.isOpen;
    this.accordion.closeOthers(this);
  }

  onDestroy() {
    this.accordion.removeGroup(this);
  }
}

using

static get parameters() {
  return [[Accordion]];
}

renders my component after making the correction noted in the first comment.

Using any of the following renders the component:

AccordionGroup.parameters = [[Accordion]];

or

AccordionGroup.parameters = [new Inject(Accordion)];

or even

// Use reflect metadata as an attempt to inject appropriate
// dependency
@Reflect.metadata('parameters', [[new InjectMetadata(Accordion)]])

but the question remain, which of these is the appropriate method to use until such a time when we can use parameter decorators with ES7.

As an aside, a lot of the code came from this particular tutorial which demonstrates all of the Angular 2 stuff with TypeScript, so I simply adapted it to my es6/es7 environment with Webpack | Migrating Directives to Angular 2

Daniel
  • 3,541
  • 3
  • 33
  • 46
datatype_void
  • 433
  • 5
  • 23
  • 1
    Your Accordion class is a component, you should pass it through `directives`, not through `providers`. And then querying it using `@ViewChildren` – Eric Martinez Jan 29 '16 at 16:16
  • Ah, thank you for that. Amazing how you miss little things such as that even after a couple days of tinkering. However, my component functionality still doesn't work. – datatype_void Jan 29 '16 at 16:21

2 Answers2

1

You need to add the @Inject() decorator on your constructor parameter. As this is not supported by the ES7 spec (only class, property and methods decorators are allowed in the current spec AFAIK), you need to have some kind of plugin for your transpiler.

If you are using Babel to transpile, you can use the babel-plugin-angular2-annotations plugin to allow this and correctly transpile the code.

import {Inject} from 'angular2/core';

export class AccordionGroup {
  constructor(@Inject(Accordion) accordion) {
    // ...
  }
}
cexbrayat
  • 17,772
  • 4
  • 27
  • 22
  • Hmm... integrating this plugin gives me the error `Uncaught Error: Cannot find module "../components/accordion/accordion.component.js"` thrown from `dom_element_schema_registry.ts`. I believe that I am properly injecting for an ES6/ES7 environment, I just can't use the `@Inject` decorator and instead must use a desugar'd version. The problem now is why the component itself isn't working. – datatype_void Jan 29 '16 at 18:43
  • As for the question of why the component itself isn't functioning, I have made this SO: https://stackoverflow.com/questions/35076907/angular-2-trouble-with-custom-components/35080334#35080334 – datatype_void Jan 29 '16 at 18:59
0

In addition to what Eric said in comment, perhaps you missed the @Inject decorator within the AccordionGroup constructor

import {Inject} from 'angular2/core';

export class AccordionGroup {
  constructor(@Inject(Accordion) accordion) {
    (...)
  }
}

Hope it helps you, Thierry

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • `@Inject` only works with `TypeScript`, there is no spec for this type of injector with `ES7` to my knowledge. Not yet at least anyway. – datatype_void Jan 29 '16 at 16:36
  • 1
    `@Inject` is a decorator provided by Angular2 core, no? The same way `@Component` is... I guess... Did you make a try? – Thierry Templier Jan 29 '16 at 16:44
  • In TypeScript decorators simply correspond to a function that is called to decorate the target (add metadata, ...)... – Thierry Templier Jan 29 '16 at 16:51
  • 1
    Yeah I started off trying to use the @Injector. However, this feature only works with TypeScript as it is not even specc'ed out in ES7 yet. Things like `constructor(@Inject(Http) http) {}` will not work outside of TypeScript. What ES6/ES7 does have are class and property decorators which is why I can transpile the decorators that are present in my code. – datatype_void Jan 29 '16 at 16:52
  • I guess using the methods I posted do indeed inject properly, I just wasn't sure what I should be doing until such a time when I can use parameter decorators, or Angular 2 provides something else. – datatype_void Jan 29 '16 at 16:56
  • 1
    A workaround could be to explicitly use the `Injector` class. You could have a look at this page: https://angular.io/docs/ts/latest/api/core/InjectMetadata-class.html. – Thierry Templier Jan 29 '16 at 17:25
  • That page did give me valuable insight, however the examples are still in `TypeScript` even for the `JavaScript` API page. I really appreciate all of your efforts though man! – datatype_void Jan 29 '16 at 18:44
  • You're welcome! The only solution I see is to manually set the metadata corresponding to constructor parameters (the ones set by the `@Inject` decorator)... – Thierry Templier Jan 30 '16 at 16:03
  • I just found out a page regarding decorator: https://docs.google.com/document/d/1uhs-a41dp2z0NLs-QiXYY-rqLGhgjmTf4iwBad2myzY/edit. Perhaps you could try this: `@Inject([Element]) constructor(element)`. That way, you wouldn't need to use parameter decorator ;-) It would be worthwhile to try... – Thierry Templier Feb 05 '16 at 09:25