Skip to content

Commit

Permalink
Angular Forms Directive (#88)
Browse files Browse the repository at this point in the history
This change allows for the use of `ngModel` and `formControlName` when using the drop-downs with Angular.

There should be no breaking changes to previous functionality or usage for either Angular or Vanilla NativeScript.
Build scripts updated. Compatible with Angular AoT and Webpack.

Based on `nativescript-angular/value-accessors/selectedIndex-value-accessor.ts` and `nativescript-angular/nativescript-angular/forms.ts` with some changes to conform to project linting.
  • Loading branch information
DickSmith authored and PeterStaev committed May 1, 2017
1 parent 108d1e4 commit 7417fdc
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 53 deletions.
68 changes: 52 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,37 +106,73 @@ export function dropDownSelectedIndexChanged(args: SelectedIndexChangedEventData
}
```

## Angular 2 Example
## Angular

##### Migration to 3.0+

- Remove:
```typescript
registerElement("DropDown", () => require("nativescript-drop-down/drop-down").DropDown);`
```
- Import `DropDownModule` in `NgModule`:
```typescript
import { DropDownModule } from “nativescript-drop-down/angular”;
@NgModule({
imports: [
DropDownModule,
],
})
```

##### Example Usage
```TypeScript
// main.ts
import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { NgModule } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { DropDownModule } from "nativescript-drop-down/angular";
import { AppComponent } from "./app.component";
import { registerElement } from "nativescript-angular/element-registry";

registerElement("DropDown", () => require("nativescript-drop-down/drop-down").DropDown);
@NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent],
imports: [NativeScriptModule],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
imports: [
NativeScriptModule,
DropDownModule,
],
})
class AppComponentModule {}
class AppComponentModule {
}
platformNativeScriptDynamic().bootstrapModule(AppComponentModule);
```

```HTML
<!-- app.component.html -->
<StackLayout>
<GridLayout rows="auto, auto, *" columns="auto, *">
<DropDown #dd backroundColor="red" [items]="items" [selectedIndex]="selectedIndex"
(selectedIndexChanged)="onchange($event)" (opened)="onopen()"
row="0" colSpan="2">
</DropDown>
<Label text="Selected Index:" row="1" col="0" fontSize="18" verticalAlignment="bottom"></Label>
<TextField [text]="selectedIndex" row="1" col="1" ></TextField>
<GridLayout rows="auto, auto, *"
columns="auto, *">
<DropDown #dd
backroundColor="red"
[items]="items"
[(ngModel)]="selectedIndex"
(selectedIndexChanged)="onchange($event)"
(opened)="onopen()"
row="0"
colSpan="2"></DropDown>
<Label text="Selected Index:"
row="1"
col="0"
fontSize="18"
verticalAlignment="bottom"></Label>
<TextField [text]="selectedIndex"
row="1"
col="1"></TextField>
</GridLayout>
</StackLayout>
```
Expand Down
78 changes: 78 additions & 0 deletions angular/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { AfterViewInit, Directive, ElementRef, HostListener, Inject, NgModule, forwardRef } from "@angular/core";
import { FormsModule, NG_VALUE_ACCESSOR } from "@angular/forms";
import { BaseValueAccessor, registerElement } from "nativescript-angular";
import { convertToInt } from "nativescript-angular/common/utils";
import { View } from "tns-core-modules/ui/core/view";

registerElement("DropDown", () => require("../drop-down").DropDown);

const SELECTED_INDEX_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectedIndexValueAccessor), multi: true};

export type SelectableView = {selectedIndex: number} & View;

/**
* The accessor for setting a selectedIndex and listening to changes that is used by the
* {@link NgModel} directives.
*
* ### Example
* ```
* <DropDown [(ngModel)]="model.test">
* ```
*/
@Directive({
// tslint:disable-next-line:max-line-length directive-selector
selector: "DropDown[ngModel], DropDown[formControlName], dropDown[ngModel], dropDown[formControlName], drop-down[ngModel], drop-down[formControlName]",
providers: [SELECTED_INDEX_VALUE_ACCESSOR]
})
export class SelectedIndexValueAccessor extends BaseValueAccessor<SelectableView> implements AfterViewInit { // tslint:disable-line:max-line-length directive-class-suffix

private _normalizedValue: number;
private viewInitialized: boolean;

constructor(@Inject(ElementRef) elementRef: ElementRef) {
super(elementRef.nativeElement);
}

@HostListener("selectedIndexChange", ["$event"])
public selectedIndexChangeListener(event: any) {
this.onChange(event.value);
}

// tslint:disable-next-line:no-empty
public onTouched = () => { };

public writeValue(value: any): void {
if (value === undefined || value === null || value === "") {
this._normalizedValue = null;
}
else {
this._normalizedValue = convertToInt(value);
}

if (this.viewInitialized) {
this.view.selectedIndex = this._normalizedValue;
}
}

public ngAfterViewInit() {
this.viewInitialized = true;
this.view.selectedIndex = this._normalizedValue;
}

public registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

@NgModule({
declarations: [ SelectedIndexValueAccessor ],
providers: [],
imports: [
FormsModule
],
exports: [
FormsModule,
SelectedIndexValueAccessor
]
})
export class DropDownModule {
}
2 changes: 2 additions & 0 deletions demo-ng/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { AppRoutingModule } from "./app.routing";
import { AppComponent } from "./app.component";

import { DropDownComponent } from "./dropdown/dropdown.component";
import { DropDownModule } from 'nativescript-drop-down/angular';

@NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule,
DropDownModule,
AppRoutingModule
],
declarations: [
Expand Down
36 changes: 26 additions & 10 deletions demo-ng/app/dropdown/dropdown.component.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
<ActionBar title="NG Demo">
</ActionBar>
<ActionBar title="NG Demo"></ActionBar>
<StackLayout>
<GridLayout rows="auto, auto, auto, *" columns="auto, *">
<DropDown #dd [class] ="cssClass" [items]="items" [selectedIndex]="selectedIndex" [hint]="hint"
(selectedIndexChanged)="onchange($event)" (opened)="onopen()"
row="0" colSpan="2">
</DropDown>
<Label text="Selected Index:" row="1" col="0" fontSize="18" verticalAlignment="bottom"></Label>
<TextField [text]="selectedIndex" row="1" col="1" ></TextField>
<Button row="2" col="0" colSpan="2" (tap)="changeStyles()" text="Change Styles" style="font-size: 25"></Button>
<GridLayout rows="auto, auto, auto, *"
columns="auto, *">
<DropDown #dd
[class]="cssClass"
[items]="items"
[(ngModel)]="selectedIndex"
[hint]="hint"
(selectedIndexChanged)="onchange($event)"
(opened)="onopen()"
row="0"
colSpan="2"></DropDown>
<Label text="Selected Index:"
row="1"
col="0"
fontSize="18"
verticalAlignment="bottom"></Label>
<TextField [text]="selectedIndex"
row="1"
col="1"></TextField>
<Button row="2"
col="0"
colSpan="2"
(tap)="changeStyles()"
text="Change Styles"
style="font-size: 25"></Button>
</GridLayout>
</StackLayout>
19 changes: 11 additions & 8 deletions demo-ng/app/dropdown/dropdown.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,30 @@ import { SelectedIndexChangedEventData, ValueList } from "nativescript-drop-down
})
export class DropDownComponent implements OnInit {
public selectedIndex: number = null;
public hint = "My Hint";
public hint = "My Hint";
public items: ValueList<string>;
public cssClass: string = "default";
public cssClass: string = "default";

public ngOnInit() {
this.items = new ValueList<string>();
for (let loop = 0; loop < 200; loop++) {
this.items.push({ value: `I${loop}`, display: `Item ${loop}`});
for ( let loop = 0; loop < 200; loop++ ) {
this.items.push({
value: `I${loop}`,
display: `Item ${loop}`,
});
}
}

public onchange(args: SelectedIndexChangedEventData) {
console.log(`Drop Down selected index changed from ${args.oldIndex} to ${args.newIndex}. New value is '${this.items.getValue(args.newIndex)}'`);
this.selectedIndex = args.newIndex;
console.log(`Drop Down selected index changed from ${args.oldIndex} to ${args.newIndex}. New value is "${this.items.getValue(
args.newIndex)}"`);
}

public onopen() {
console.log("Drop Down opened.");
}

public changeStyles() {
this.cssClass = "changed-styles";
}
}
}
4 changes: 0 additions & 4 deletions demo-ng/app/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";

import { registerElement } from "nativescript-angular/element-registry";

import { AppModule } from "./app.module";

registerElement("DropDown", () => require("nativescript-drop-down/drop-down").DropDown);

platformNativeScriptDynamic().bootstrapModule(AppModule);
18 changes: 9 additions & 9 deletions demo-ng/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
"debug-ios": "npm uninstall nativescript-drop-down && tns debug ios --emulator"
},
"dependencies": {
"@angular/animations": "4.0.0",
"@angular/common": "4.0.0",
"@angular/compiler": "4.0.0",
"@angular/core": "4.0.0",
"@angular/forms": "4.0.0",
"@angular/http": "4.0.0",
"@angular/platform-browser": "4.0.0",
"@angular/platform-browser-dynamic": "4.0.0",
"@angular/router": "4.0.0",
"@angular/animations": "^4.0.3",
"@angular/common": "^4.0.3",
"@angular/compiler": "^4.0.3",
"@angular/core": "^4.0.3",
"@angular/forms": "^4.0.3",
"@angular/http": "^4.0.3",
"@angular/platform-browser": "^4.0.3",
"@angular/platform-browser-dynamic": "^4.0.3",
"@angular/router": "^4.0.3",
"nativescript-angular": "rc",
"nativescript-drop-down": "file:../bin/dist",
"reflect-metadata": "~0.1.8",
Expand Down
12 changes: 6 additions & 6 deletions drop-down.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,6 @@ export class DropDown extends DropDownBase {
spinner.setOnTouchListener(touchListener);
(spinner as any).touchListener = touchListener;

// When used in templates the selectedIndex changed event is fired before the native widget is init.
// So here we must set the inital value (if any)
if (!types.isNullOrUndefined(this.selectedIndex)) {
this.android.setSelection(this.selectedIndex + 1); // +1 for the hint first element
}

return spinner;
}

Expand All @@ -89,6 +83,12 @@ export class DropDown extends DropDownBase {
nativeView.adapter.owner = new WeakRef(this);
nativeView.itemSelectedListener.owner = new WeakRef(this);
nativeView.touchListener.owner = new WeakRef(this);

// When used in templates the selectedIndex changed event is fired before the native widget is init.
// So here we must set the inital value (if any)
if (!types.isNullOrUndefined(this.selectedIndex)) {
this.android.setSelection(this.selectedIndex + 1); // +1 for the hint first element
}
}

public disposeNativeView() {
Expand Down
4 changes: 4 additions & 0 deletions gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
tsCompile: {
cmd: "node ./node_modules/typescript/bin/tsc --project tsconfig.json --outDir " + localConfig.outDir
},
ngCompile: {
cmd: "node ./node_modules/.bin/ngc --project tsconfig.aot.json --outDir " + localConfig.outDir
},
tslint: {
cmd: "node ./node_modules/tslint/bin/tslint --project tsconfig.json"
},
Expand All @@ -65,6 +68,7 @@
"exec:tslint",
"clean:build",
"exec:tsCompile",
"exec:ngCompile",
"copy"
]);
grunt.registerTask("publish", [
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
},

"devDependencies": {
"@angular/compiler-cli": "^4.0.3",
"@angular/core": "^4.0.3",
"@angular/forms": "^4.0.3",
"nativescript-angular": "rc",
"typescript": "~2.2.2",
"tslint": "^4.5.1",
"tns-core-modules": "rc",
Expand Down
30 changes: 30 additions & 0 deletions tsconfig.aot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"noEmitOnError": true,
"noEmitHelpers": true,
"sourceMap": false,
"removeComments": true,
"declaration": true,
"experimentalDecorators": true,
"target": "es5",
"module": "commonjs",
"outDir": "bin/dist",
"lib": ["es6", "dom", "es2015.iterable"],
"rootDir": ".",
"baseUrl": ".",
"paths": {
"*": [
"./node_modules/tns-core-modules/*",
"./node_modules/*"
]
},
"emitDecoratorMetadata": true,
"moduleResolution": "node"
},
"files": [
"angular/index.ts"
],
"angularCompilerOptions": {
"skipTemplateCodegen": true
}
}

0 comments on commit 7417fdc

Please sign in to comment.