How do I properly connect two directives, or a directive to a component (which is a directive too) in angular2 in the "angular way of writing code"?
Since the documentation on angular2 is still quite scarce, any insight or reference is greatly appreciated.
This is what every angular2 example shows - binding to a string
via ngModel
:
@Component({
template: 'Hello <input type="text" [(ngModel)]="myVariable">!'
})
class ExampleComponent() {
myVariable: string = 'World';
}
Suppose I want to use ngModel
on a custom component which does not represent string
s, but any other value, for example a number
or a class or interface:
interface Customer {
name: string;
company: string;
phoneNumbers: string[];
addresses: Address[];
}
@Component({
selector: 'customer-editor',
template: `
<p>Customer editor for {{customer.name}}</p>
<div><input [(ngModel)]="customer.name"></div>`
})
class CustomerEditor {
customer: Customer;
}
Why do I use ngModel
, you may ask, when any other attribute would make data binding a lot easier? Well, I am implementing a design shim for angular2, and the components would be used like (or alongside) native <input>
s:
<input name="name" [(ngModel)]="user.name">
<pretty-select name="country" [(ngModel)]="user.country" selectBy="countryCode">
<option value="us">United States of America</option>
<option value="uk">United Kingdom</option>
...
</pretty-select>
user.country
would be an object, not a string:
interface Country {
countryCode: string,
countryName: string
}
class User {
name: string;
country: Country;
...
}
What I have got working so far, but "feels wrong":
GitHub repository for this example
To link up the reference supplied to the ngModel
directive with my CustomerEditor
component, currently I am using my own ControlValueAccessor
: (simplified)
const CUSTOMER_VALUE_ACCESSOR: Provider = CONST_EXPR(
new Provider(NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => CustomerValueAccessor)
})
);
@Directive({
selector: 'customer-editor[ngModel]',
providers: [CUSTOMER_VALUE_ACCESSOR]
})
@Injectable()
class CustomerValueAccessor implements ControlValueAccessor {
private host: CustomerEditor;
constructor(element: ElementRef, viewManager: AppViewManager) {
let hostComponent: any = viewManager.getComponent(element);
if (hostComponent instanceof CustomerEditor) {
this.host = hostComponent;
}
}
writeValue(value: any): void {
if (this.host) { this.host.setCustomer(value); }
}
}
Now, what disturbs me about that ControlValueAccessor
is the way I get a reference to my host component:
if (hostComponent instanceof CustomerEditor) {
this.host = hostComponent;
}
This not only requires 3 dependencies where one should suffice (ElementRef
, AppViewManager
, CustomerEditor
), it also feels very wrong to do type-checking during runtime.
How is the "proper" way to get a reference to the host component in angular2?
Other things I have tried, but not have gotten to work:
This answer by Thierry Templier notes the component class in the constructor of the ControlValueAccessor for it to be injected by angular:
class CustomerValueAccessor implements ControlValueAccessor { constructor(private host: CustomerEditor) { } }
Unfortunately, that does not work for me, and gives me an exception:
Cannot resolve all parameters for 'CustomerValueAccessor'(undefined). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'CustomerValueAccessor' is decorated with Injectable.
Using
@Host
:class CustomerValueAccessor implements ControlValueAccessor { constructor(@Host() private editor: CustomerEditor) { } }
Throws the same exception as the solution above.
Using
@Optional
:class CustomerValueAccessor implements ControlValueAccessor { constructor(@Optional() private editor: CustomerEditor) { } }
Does not throw an exception, but
CustomerEditor
is not injected and staysnull
.
Since angular changed/changes very frequently, the specific versions I am working with might be relevant, which is angular2@2.0.0-beta.6
.