Brandon
Brandon

Reputation: 1605

Writing extensible components in Angular 2

I'm currently using Angular2 (Dart) to create a reusable search box component, which includes a suggestion box.

The trouble I'm running into is that I'm not sure how to make the suggestion box extensible by supporting richer suggestion types, such as suggestions with icons, suggestions with multiple lines of text, varying styles on the text, animations, etc.

Currently the suggestion box HTML (stripped down) looks a bit like this:

<div class="suggestion-box-container">
  <div class="suggestion" *ng-for="#suggestion of model.suggestions">
    {{ suggestion.text }}
  </div>
</div>

And the Dart code looks sort of like this:

@Component(
  selector: 'suggestion-box'
)
@View(
  templateUrl: 'suggestion_box.html',
  directives: const [NgFor]
)
class SuggestionBox {
  final SuggestionModel model;
  // ...
}

class SuggestionModel {
  List<Suggestion> suggestions = [];
  // ...
}

class Suggestion {
  String text;
  // ...
}

I'm wondering how I can modify this design so that a user can extend Suggestion and write their own corresponding suggestion component.

I considered something like

<basic-suggestion *ng-if="suggestion.type == 'basic'"></basic-suggestion>
<icon-suggestion *ng-if="suggestion.type == 'icon'"></icon-suggestion>
...

But of course that would get out of hand quickly as more suggestion types are supported, and would also require duplicating the (^hover) and (^click) handlers to each type of suggestion. Worse yet, it would require modifying the original library rather than just using it in a modular way.

Is there an elegant solution to this sort of thing? Or would users simply be better off writing their own suggestion box entirely if they wanted richer suggestions? I'm hoping to avoid that since I want to bake in a bunch of accessibility functionality into the search box/suggestion box that the user shouldn't need to rewrite.

EDIT: based on Gunter's answer I've managed to improve this by a little bit:

<suggestion-box>
  <div *ng-for="#suggestion in suggestions">
    <suggestion suggestion-instance="suggestion">
      <!-- Custom suggestion code goes here.
           The following is an example -->
      <img src="{{suggestion.iconSrc}}"></img> {{suggestion.text}}
    </suggestion>
  </div>
</suggestion>

I have a new suggestion component that looks a bit like this:

@Component(
  selector: 'suggestion',
  properties: const ['suggestion: suggestion-instance']
)
@View(
  template: '''
<div (^click)="handleClick(suggestion)"
     (hover)="handleHover(suggestion)">
  <content></content>
</div>
'''
)
class SuggestionComponent {
  final SuggestionModel model;
  final Suggestion suggestion;
  // ...
}

This is a bit nicer now because the user now gets accessibility/keyboard control/mouse control out of the box. But this still isn't ideal. I'd somehow like the template to automatically determine the appropriate component to use based on the actual subclass of the suggestion. I suppose I could define a method in each component like getTemplate() but that probably wouldn't be a very Angular-esque way of doing it.

Upvotes: 2

Views: 1210

Answers (1)

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657118

I haven't really used Angular2 but I think there are at least two ways to do it - You should be able to extend a component (just create a derived class and specialize the behavior - Use composition with the <content> tag of the shadow DOM. I found some docs here https://www.airpair.com/angularjs/posts/creating-components-p3-angular2-directives - Or a combination of the two

update

Support for extending components is coming with version 3.2. Annotations like @HostBinding(), @HostListener(), @ViewChild(), @ContentChild() will be recognised on subclasses, so will be interfaces for OnInit and other lifecycle callbacks

There are different ways beside inheritanc to make a component customizable.
You can pass component types to be instantiated by your search box using DynamicComponentLoader,
pass children to be displayed at <ng-content> locations,
or pass <template> content to be added dynamically by your search component, and probably others.

Upvotes: 1

Related Questions