Is there a proper way to connect two directives, or a directive to a component (which is a directive as well) in angular2 following the "angular way of writing code"?
Given the limited documentation on angular2, any insights or references on this topic would be greatly appreciated.
Every angular2 example typically demonstrates binding to a string
using ngModel
:
@Component({
template: 'Hello <input type="text" [(ngModel)]="myVariable">!'
})
class ExampleComponent() {
myVariable: string = 'World';
}
Now, suppose I want to use ngModel
on a custom component in angular2, where the custom component does not represent string
s but other values like number
, classes, or interfaces:
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;
}
But why use ngModel
when there are simpler data binding options available? In this case, it's for implementing a design shim for angular2, where components are used similar to native <input>
elements:
<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>
In this case, user.country
would be an object instead of a string:
interface Country {
countryCode: string,
countryName: string
}
class User {
name: string;
country: Country;
...
}
Current Solution, but with Some Concerns:
GitHub repository for this example
To establish the connection between the reference supplied to the ngModel
directive and my CustomerEditor
component, I'm currently utilizing my custom 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); }
}
}
However, my main concern with this ControlValueAccessor
is the way I retrieve a reference to the host component:
if (hostComponent instanceof CustomerEditor) {
this.host = hostComponent;
}
This approach not only involves 3 dependencies when one should suffice (ElementRef
, AppViewManager
, CustomerEditor
), but it also feels incorrect to perform type-checking during runtime.
What is the proper method to obtain a reference to the host component in angular2?
Other Approaches Attempted, but Unsuccessful:
This answer by Thierry Templier suggests including the component class in the constructor of the ControlValueAccessor for automatic injection by angular:
class CustomerValueAccessor implements ControlValueAccessor { constructor(private host: CustomerEditor) { } }
Unfortunately, this method did not work for me and resulted in 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) { } }
This approach also led to the same exception as the previous one.
Attempting
@Optional
:class CustomerValueAccessor implements ControlValueAccessor { constructor(@Optional() private editor: CustomerEditor) { } }
While this did not throw an exception,
CustomerEditor
remained uninitialized and null.
Given the frequent changes in angular, the specific versions being used may be relevant, which is
.<span class="__cf_email__" data-cfemail="4e2f20293b222f3c7c0e7c607e607e632c2b3a2f6078">[email protected]</span>