├── .github
└── workflows
│ └── nodejs.yml
├── LICENSE
├── README.md
├── old
└── article.md
└── workspace
├── .editorconfig
├── .gitignore
├── angular.json
├── package-lock.json
├── package.json
├── projects
├── date-value-accessor
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ │ ├── lib
│ │ │ ├── date-value-accessor.module.ts
│ │ │ ├── date-value-accessor.reactive.spec.ts
│ │ │ ├── date-value-accessor.template.spec.ts
│ │ │ ├── date-value-accessor.ts
│ │ │ ├── default-value-accessor.template.spec.ts
│ │ │ ├── iso-date-value-accessor.module.ts
│ │ │ ├── iso-date-value-accessor.reactive.spec.ts
│ │ │ ├── iso-date-value-accessor.template.spec.ts
│ │ │ ├── iso-date-value-accessor.ts
│ │ │ ├── local-date-value-accessor.module.ts
│ │ │ ├── local-date-value-accessor.reactive.spec.ts
│ │ │ ├── local-date-value-accessor.template.spec.ts
│ │ │ ├── local-date-value-accessor.ts
│ │ │ ├── local-iso-date-value-accessor.module.ts
│ │ │ ├── local-iso-date-value-accessor.reactive.spec.ts
│ │ │ ├── local-iso-date-value-accessor.template.spec.ts
│ │ │ ├── local-iso-date-value-accessor.ts
│ │ │ └── spec-utils.ts
│ │ ├── public-api.ts
│ │ └── test.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ └── tslint.json
└── demo
│ ├── .browserslistrc
│ ├── karma.conf.js
│ ├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── demo-with-dates
│ │ │ ├── reactive-form
│ │ │ │ ├── reactive-form.component.html
│ │ │ │ └── reactive-form.component.ts
│ │ │ └── template-driven-form
│ │ │ │ ├── template-driven-form.component.html
│ │ │ │ └── template-driven-form.component.ts
│ │ ├── demo-with-strings
│ │ │ ├── reactive-form-iso
│ │ │ │ ├── reactive-form-iso.component.html
│ │ │ │ └── reactive-form-iso.component.ts
│ │ │ └── template-driven-form-iso
│ │ │ │ ├── template-driven-form-iso.component.html
│ │ │ │ └── template-driven-form-iso.component.ts
│ │ └── shared
│ │ │ ├── explanation-date-value-accessor.component.ts
│ │ │ ├── explanation-default-value-accessor.component.ts
│ │ │ ├── explanation-iso-date-value-accessor.component.ts
│ │ │ ├── explanation-local-date-value-accessor.component.ts
│ │ │ ├── explanation-local-iso-date-value-accessor.component.ts
│ │ │ ├── release-with-iso-string.ts
│ │ │ └── release.ts
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── ie11.png
│ │ ├── reactive-does-not-work.gif
│ │ ├── reactive-works-local.gif
│ │ ├── reactive-works.gif
│ │ ├── screenshot.png
│ │ └── screenshot_simple.png
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ └── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── tsconfig.json
└── tslint.json
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Tests
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [14.x, 16.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v1
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - run: cd workspace && npm ci
28 | - run: cd workspace && npm run ng -- test --project date-value-accessor --watch=false --browsers=ChromeHeadless
29 | env:
30 | CI: true
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2021 Johannes Hoppe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DateValueAccessor for Angular
2 | [![NPM version][npm-image]][npm-url]
3 | [![Tests][tests-image]][tests-url]
4 |
5 | A set of `ControlValueAccessor`s for Angular to work with Browser's native date input elements.
6 | Now you can use `` directly with two-way data bindings (`ngModel`) as well as with reactive forms (`formControlName`/`formControl`). Choose freely between date objects and ISO-formatted strings.
7 |
8 | ## Releases
9 |
10 | The latest version is v3.0.0. Please refer to the [releases page](https://github.com/JohannesHoppe/angular-date-value-accessor/releases) to see which version of the package is compatible with which version of Angular and to find out what new features were rolled out at which point in time.
11 |
12 | ## Demo
13 |
14 | Here you can see the `DateValueAccessor` - the binding works!
15 | Changes to the input field are propagated to the model.
16 |
17 | 
18 |
19 | And here you can see the `LocalDateValueAccessor` ⭐️.
20 | Please notice how the date is adjusted due to the German time zone (UTC+1) and how the time offset works.
21 |
22 | 
23 |
24 | And this shows a not working form field (the default behaviour).
25 | Changes in the input field are propagated to the model, but unfortunately the date becomes a string which is not very useful for any further processing.
26 |
27 | 
28 |
29 | **You can try out a full demo at the following page:**
30 | **→ http://johanneshoppe.github.io/angular-date-value-accessor/**
31 |
32 |
33 | ## Installation
34 |
35 | Download the package via NPM:
36 |
37 | ```bash
38 | npm install angular-date-value-accessor
39 | ```
40 |
41 |
42 | ## UTC Time or Local Time
43 | When working with Dates in Javascript you either operate in UTC or Local Time.
44 |
45 | * UTC has no timezone offset.
46 | * Local Time depends on the host system time zone and offset.
47 |
48 | Javascript Dates support both the UTC and the Local Time representation.
49 | Depending on the requirements of your application you can choose from these Value Accessors:
50 | * [DateValueAccessor (UTC)](#datevalueaccessor-utc)
51 | * [LocalDateValueAccessor (Local Time) ⭐️](#localdatevalueaccessor-local-time)
52 | * [IsoDateValueAccessor (UTC as ISO 8601 string)](#isodatevalueaccessor-utc-as-iso-8601-string)
53 | * [LocalIsoDateValueAccessor (Local Time as ISO 8601 string)](#localisodatevalueaccessor-local-time-as-iso-8601-string)
54 |
55 |
56 | > **ℹ️ Hint:** Most UI component libraries like Angular Material, Kendo Angular, PrimeNG implement their DatePickers operating in Local Time. The Angular Date Pipe uses the Local Time representation of the Date Object by default, too.
57 |
58 | ## Installation & Usage
59 |
60 | You have to explicitly opt-in by adding one of these attribute directives to a HTML date input control: `useValueAsDate`, `useValueAsLocalDate`, `useValueAsIso` or `useValueAsLocalIso`.
61 |
62 |
63 | ### DateValueAccessor (UTC)
64 |
65 | The original `DateValueAccessor` operates in UTC (Coordinated Universal Time).
66 | The HTML date input will use the UTC representation of a given Date Object.
67 | When you select a date it will output an UTC date with the time set to 00:00 (UTC).
68 |
69 | **If you are unsure what to use, use the `LocalDateValueAccessor` and not the `DateValueAccessor`.**
70 | **Most users will expect the input field to correlate to their local clock.**
71 |
72 | Import the standalone directive *or* the NgModule:
73 |
74 | ```js
75 | // app.module.ts
76 |
77 | import { DateValueAccessor, DateValueAccessorModule } from 'angular-date-value-accessor';
78 |
79 | @NgModule({
80 | imports: [
81 | DateValueAccessor
82 | // OR
83 | DateValueAccessorModule
84 | ]
85 | })
86 | export class AppModule {}
87 | ```
88 |
89 | Now you can apply the `useValueAsDate` to your date input controls:
90 |
91 | ```html
92 |
93 |
94 |
98 |
99 | OR
100 |
101 |
104 | ```
105 |
106 |
107 | ### LocalDateValueAccessor (Local Time) ⭐️
108 |
109 | The improved `LocalDateValueAccessor` operates in your Local Time.
110 | The HTML date input will use the Local Time representation of a given the Date Object.
111 | When you select a date it will output a Local Date with the time set to 00:00 (Local Time).
112 |
113 | Import the standalone directive *or* the NgModule:
114 |
115 | ```js
116 | // app.module.ts
117 |
118 | import { LocalDateValueAccessor, LocalDateValueAccessorModule } from 'angular-date-value-accessor';
119 |
120 | @NgModule({
121 | imports: [
122 | LocalDateValueAccessor
123 | // OR
124 | LocalDateValueAccessorModule
125 | ]
126 | })
127 | export class AppModule {}
128 | ```
129 |
130 | Now you can apply the `useValueAsLocalDate` to your date input controls:
131 |
132 | ```html
133 |
134 |
135 |
139 |
140 | OR
141 |
142 |
145 | ```
146 |
147 |
148 | ### IsoDateValueAccessor (UTC as ISO 8601 string)
149 |
150 | This directive gets and sets ISO 8601 formatted date strings in HTML date inputs.
151 | The handling of the dates is the same as for the `DateValueAccessor`.
152 |
153 | The `IsoDateValueAccessor` operates in UTC (Coordinated Universal Time).
154 | The HTML date input will use the UTC representation of a given ISO 8601 formatted date string.
155 | When you select a date it will output an ISO-formatted string with the time set to 00:00 (UTC).
156 |
157 | Import the standalone directive *or* the NgModule:
158 |
159 | ```js
160 | // app.module.ts
161 | import { IsoDateValueAccessor, IsoDateValueAccessorModule } from 'angular-date-value-accessor';
162 |
163 | @NgModule({
164 | imports: [
165 | IsoDateValueAccessor
166 | // OR
167 | IsoDateValueAccessorModule
168 | ]
169 | })
170 | export class AppModule { }
171 | ```
172 |
173 | Now you can apply the `useValueAsIso` to your date input controls:
174 |
175 | ```html
176 |
177 |
178 |
182 |
183 | OR
184 |
185 |
188 | ```
189 |
190 |
191 | ### LocalIsoDateValueAccessor (Local Time as ISO 8601 string)
192 |
193 | This directive gets and sets ISO 8601 formatted date strings in HTML date inputs.
194 | The handling of the dates is the same as for the `LocalDateValueAccessor`.
195 |
196 | The `LocalIsoDateValueAccessor` operates in your Local Time.
197 | The HTML date input will use the Local Time representation of a given ISO 8601 formatted date string.
198 | When you select a date it will output an ISO-formatted string with a time that equals to 00:00 (Local Time).
199 |
200 | Note: The timezone of the outputted string is always zero UTC offset, as denoted by the suffix "Z".
201 |
202 | Import the standalone directive *or* the NgModule:
203 |
204 | ```js
205 | // app.module.ts
206 | import { LocalIsoDateValueAccessor, LocalIsoDateValueAccessorModule } from 'angular-date-value-accessor';
207 |
208 | @NgModule({
209 | imports: [
210 | LocalIsoDateValueAccessor
211 | // OR
212 | LocalIsoDateValueAccessorModule
213 | ]
214 | })
215 | export class AppModule { }
216 | ```
217 |
218 | Now you can apply the `useValueAsLocalIso` to your date input controls:
219 |
220 | ```html
221 |
222 |
223 |
227 |
228 | OR
229 |
230 |
233 | ```
234 |
235 |
236 | ## License
237 |
238 | This code is published under the [MIT license](LICENSE).
239 |
240 |
241 |
242 | [npm-url]: https://npmjs.org/package/angular-date-value-accessor
243 | [npm-image]: https://badge.fury.io/js/angular-date-value-accessor.svg
244 | [tests-url]: https://github.com/JohannesHoppe/angular-date-value-accessor/actions?query=workflow%3ATests
245 | [tests-image]: https://github.com/JohannesHoppe/angular-date-value-accessor/workflows/Tests/badge.svg
246 |
--------------------------------------------------------------------------------
/old/article.md:
--------------------------------------------------------------------------------
1 | # Angular 2: How to use date input controls with Angular Forms
2 | [![NPM version][npm-image]][npm-url]
3 |
4 | [](http://johanneshoppe.github.io/angular-date-value-accessor/)
5 |
6 |
7 | ## The problem
8 |
9 | Working with forms is pretty easy in Angular 2.
10 | You just need to decide between Template-Driven and Reactive Forms and you are ready to start with some bindings and validation. The following code shows a two-way data binding with `ngModel` against a property of type `string`:
11 |
12 | ```
13 |
14 | ```
15 |
16 | But there is one problem to tackle: models of type `Date`!
17 | You might wonder, because HTML5 date input controls are not working as expected:
18 |
19 | ```
20 |
21 | ```
22 |
23 | Even if `myBirtday` contains a valid date, the date input control is not rendering the value at all.
24 | In fact, we are supposed to set a string that is representing a full-date as defined in [RFC 3339](https://www.w3.org/TR/html-markup/references.html#refsRFC3339). The same string is written back to the model, when changes have been made, e.g. "2016-10-13". This behavior is specified in the [W3C HTML language reference for `inputEl.value`](https://www.w3.org/TR/html-markup/input.date.html#input.date.attrs.value). According to the specification, date input controls are based on strings. So what can we do to keep the Date type?
25 |
26 | ## A solution
27 |
28 | Let's review the possible solutions:
29 |
30 | 1. We could create a [custom form control](http://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html). This would be a clean and extendable solution, but it might lead to more code than required for the given use-case.
31 |
32 | 2. We could convert the strings directly in our `@Component` as described [here](http://stackoverflow.com/a/37055451). But do we really want to bloat our "business code layer" with boilerplate code?
33 |
34 | 3. We could create a __custom value accessor__. The following article discusses this option.
35 |
36 | It turns out, that date input control has another, not that well-known property: [`inputEl.valueAsDate`](https://www.w3.org/TR/2012/WD-html5-20121025/common-input-element-apis.html#dom-input-valueasdate)! The `inputEl.valueAsDate` attribute represents the value (still a string) of the element, interpreted as a date. This is exactly what we need. Now we only need to convince Angular to use this property, instead of `inputEl.value`.
37 |
38 | Fortunately Angular 2 is very expansible here. FormControls (both template-driven and reactive) subscribe for values and write values via Directives that implement `ControlValueAccessor`. Take a look at the relevant method [selectValueAccessor](https://github.com/angular/angular/blob/2.1.0-beta.0/modules/%40angular/forms/src/directives/shared.ts#L140), which is used in all necessary directives. Normal input controls (e.g. ``) or textareas are handled by the [DefaultValueAccessor](https://github.com/angular/angular/blob/2.1.0-beta.0/modules/%40angular/forms/src/directives/default_value_accessor.ts). Another example is the [CheckboxValueAccessor](https://github.com/angular/angular/blob/2.1.0-beta.0/modules/%40angular/forms/src/directives/checkbox_value_accessor.ts) which is applied to checkbox input controls.
39 |
40 | The job isn't complicated at all. We just need to implement a new value accessor for date input controls.
41 | `DateValueAccessor` is a nice name:
42 |
43 | ```js
44 | // date-value-accessor.ts
45 |
46 | import { Directive, ElementRef, HostListener, Renderer, forwardRef } from '@angular/core';
47 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
48 |
49 | export const DATE_VALUE_ACCESSOR: any = {
50 | provide: NG_VALUE_ACCESSOR,
51 | useExisting: forwardRef(() => DateValueAccessor),
52 | multi: true
53 | };
54 |
55 | /**
56 | * The accessor for writing a value and listening to changes on a date input element
57 | *
58 | * ### Example
59 | * ``
60 | */
61 | @Directive({
62 | selector: '[useValueAsDate]',
63 | providers: [DATE_VALUE_ACCESSOR]
64 | })
65 | export class DateValueAccessor implements ControlValueAccessor {
66 |
67 | @HostListener('input', ['$event.target.valueAsDate']) onChange = (_: any) => { };
68 | @HostListener('blur', []) onTouched = () => { };
69 |
70 | constructor(private _renderer: Renderer, private _elementRef: ElementRef) { }
71 |
72 | writeValue(value: Date): void {
73 | this._renderer.setElementProperty(this._elementRef.nativeElement, 'valueAsDate', value);
74 | }
75 |
76 | registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
77 | registerOnTouched(fn: () => void): void { this.onTouched = fn; }
78 |
79 | setDisabledState(isDisabled: boolean): void {
80 | this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
81 | }
82 | }
83 |
84 | ```
85 |
86 | We attach the `DateValueAccessor` to the multi-provider `DATE_VALUE_ACCESSOR`, so that [selectValueAccessor](https://github.com/angular/angular/blob/2.1.0-beta.0/modules/%40angular/forms/src/directives/shared.ts#L140) can find it.
87 |
88 | The only question is, which selector should be used. I decided for an opt-in solution. Here the DateValueAccessor selects on the attribute "useValueAsDate".
89 |
90 | ```html
91 |
92 |
93 | OR
94 |
95 |
96 |
97 | OR
98 |
99 |
100 | ```
101 |
102 | It is also possible to fix the default implementation. The following selector would activate the feature magically.
103 |
104 | ```js
105 | // this selector changes the previous behavior silently and might break existing code
106 | selector: 'input[type=date][formControlName],input[type=date][formControl],input[type=date][ngModel]'
107 | ```
108 |
109 | But please be aware, that this might break existing implementations that rely of the old behaviour. So I would go for the opt-in version!
110 |
111 | ## It's all on NPM
112 |
113 | For your convenience, I created the project [`angular-data-value-accessor`](https://github.com/JohannesHoppe/angular-date-value-accessor) on Github.
114 | There is also a NPM package available:
115 |
116 |
117 | ```bash
118 | npm install --save angular-date-value-accessor
119 | ```
120 |
121 | Then import the module via NgModule:
122 |
123 | ```js
124 | // app.module.ts
125 |
126 | import { DateValueAccessorModule } from 'angular-date-value-accessor';
127 |
128 | @NgModule({
129 | imports: [
130 | DateValueAccessorModule
131 | ]
132 | })
133 | export class AppModule { }
134 | ```
135 |
136 | ## Demo
137 |
138 | Of course, there is a demo at:
139 | http://johanneshoppe.github.io/angular-date-value-accessor/
140 |
141 | [npm-url]: https://npmjs.org/package/angular-date-value-accessor
142 | [npm-image]: https://badge.fury.io/js/angular-date-value-accessor.svg
--------------------------------------------------------------------------------
/workspace/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/workspace/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.angular/cache
36 | /.sass-cache
37 | /connect.lock
38 | /coverage
39 | /libpeerconnection.log
40 | npm-debug.log
41 | yarn-error.log
42 | testem.log
43 | /typings
44 |
45 | # System Files
46 | .DS_Store
47 | Thumbs.db
48 |
49 | projects/date-value-accessor/README.md
50 |
--------------------------------------------------------------------------------
/workspace/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "date-value-accessor": {
7 | "projectType": "library",
8 | "root": "projects/date-value-accessor",
9 | "sourceRoot": "projects/date-value-accessor/src",
10 | "prefix": "lib",
11 | "architect": {
12 | "build": {
13 | "builder": "@angular-devkit/build-angular:ng-packagr",
14 | "options": {
15 | "tsConfig": "projects/date-value-accessor/tsconfig.lib.json",
16 | "project": "projects/date-value-accessor/ng-package.json"
17 | },
18 | "configurations": {
19 | "production": {
20 | "tsConfig": "projects/date-value-accessor/tsconfig.lib.prod.json"
21 | }
22 | }
23 | },
24 | "test": {
25 | "builder": "@angular-devkit/build-angular:karma",
26 | "options": {
27 | "main": "projects/date-value-accessor/src/test.ts",
28 | "tsConfig": "projects/date-value-accessor/tsconfig.spec.json",
29 | "karmaConfig": "projects/date-value-accessor/karma.conf.js"
30 | }
31 | }
32 | }
33 | },
34 | "demo": {
35 | "projectType": "application",
36 | "schematics": {},
37 | "root": "projects/demo",
38 | "sourceRoot": "projects/demo/src",
39 | "prefix": "app",
40 | "architect": {
41 | "build": {
42 | "builder": "@angular-devkit/build-angular:browser",
43 | "options": {
44 | "outputPath": "dist/demo",
45 | "index": "projects/demo/src/index.html",
46 | "main": "projects/demo/src/main.ts",
47 | "polyfills": "projects/demo/src/polyfills.ts",
48 | "tsConfig": "projects/demo/tsconfig.app.json",
49 | "assets": [
50 | "projects/demo/src/favicon.ico",
51 | "projects/demo/src/assets"
52 | ],
53 | "styles": [
54 | "projects/demo/src/styles.css"
55 | ],
56 | "scripts": [],
57 | "vendorChunk": true,
58 | "extractLicenses": false,
59 | "buildOptimizer": false,
60 | "sourceMap": true,
61 | "optimization": false,
62 | "namedChunks": true
63 | },
64 | "configurations": {
65 | "production": {
66 | "fileReplacements": [
67 | {
68 | "replace": "projects/demo/src/environments/environment.ts",
69 | "with": "projects/demo/src/environments/environment.prod.ts"
70 | }
71 | ],
72 | "optimization": true,
73 | "outputHashing": "all",
74 | "sourceMap": false,
75 | "namedChunks": false,
76 | "extractLicenses": true,
77 | "vendorChunk": false,
78 | "buildOptimizer": true,
79 | "budgets": [
80 | {
81 | "type": "initial",
82 | "maximumWarning": "2mb",
83 | "maximumError": "5mb"
84 | },
85 | {
86 | "type": "anyComponentStyle",
87 | "maximumWarning": "6kb",
88 | "maximumError": "10kb"
89 | }
90 | ]
91 | }
92 | },
93 | "defaultConfiguration": ""
94 | },
95 | "serve": {
96 | "builder": "@angular-devkit/build-angular:dev-server",
97 | "options": {
98 | "browserTarget": "demo:build"
99 | },
100 | "configurations": {
101 | "production": {
102 | "browserTarget": "demo:build:production"
103 | }
104 | }
105 | }
106 | }
107 | }}
108 | }
109 |
--------------------------------------------------------------------------------
/workspace/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workspace",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve --project demo",
7 | "prebuild": "cd .. && npx copyfiles README.md workspace/projects/date-value-accessor",
8 | "publish-library": "npm run build && cd dist/date-value-accessor && npm publish",
9 | "publish-demo": "ng build --configuration production --project demo --base-href /angular-date-value-accessor/ && npx angular-cli-ghpages --dir dist/demo",
10 | "build": "ng build --configuration production --project date-value-accessor",
11 | "test": "ng test --project date-value-accessor --watch=false",
12 | "lint": "ng lint",
13 | "e2e": "ng e2e"
14 | },
15 | "private": true,
16 | "dependencies": {
17 | "@angular/animations": "^14.2.7",
18 | "@angular/common": "^14.2.7",
19 | "@angular/compiler": "^14.2.7",
20 | "@angular/core": "^14.2.7",
21 | "@angular/forms": "^14.2.7",
22 | "@angular/platform-browser": "^14.2.7",
23 | "@angular/platform-browser-dynamic": "^14.2.7",
24 | "@angular/router": "^14.2.7",
25 | "configurable-date-input-polyfill": "^2.7.2",
26 | "rxjs": "~6.6.0",
27 | "tslib": "^2.0.0",
28 | "zone.js": "~0.11.4"
29 | },
30 | "devDependencies": {
31 | "@angular-devkit/build-angular": "^14.2.6",
32 | "@angular/cli": "^14.2.6",
33 | "@angular/compiler-cli": "^14.2.7",
34 | "@types/jasmine": "~3.6.0",
35 | "@types/jasminewd2": "~2.0.3",
36 | "@types/node": "^12.11.1",
37 | "codelyzer": "^6.0.0",
38 | "jasmine-core": "~3.6.0",
39 | "jasmine-spec-reporter": "~5.0.0",
40 | "karma": "~6.3.4",
41 | "karma-chrome-launcher": "~3.1.0",
42 | "karma-coverage-istanbul-reporter": "~3.0.2",
43 | "karma-jasmine": "~4.0.0",
44 | "karma-jasmine-html-reporter": "^1.5.0",
45 | "ng-packagr": "^14.2.1",
46 | "ts-node": "~8.3.0",
47 | "tslint": "~6.1.0",
48 | "typescript": "~4.7.4"
49 | }
50 | }
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage/date-value-accessor'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/date-value-accessor",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-date-value-accessor",
3 | "version": "3.0.0",
4 | "description": "A set of ControlValueAccessors for Angular to work with Browser's native date input elements. Now you can use with template-driven and reactive forms!",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/johanneshoppe/angular-date-value-accessor.git"
8 | },
9 | "keywords": [
10 | "angular",
11 | "forms",
12 | "date",
13 | "ControlValueAccessor",
14 | "DateValueAccessor",
15 | "LocalDateValueAccessor",
16 | "LocalIsoDateValueAccessor",
17 | "UTC",
18 | "Local Time"
19 | ],
20 | "author": {
21 | "name": "Johannes Hoppe",
22 | "email": "johannes.hoppe@haushoppe-its.de"
23 | },
24 | "contributors": [
25 | {
26 | "name": "Ferdinand Malcher",
27 | "email": "ferdinand@malcher.media"
28 | }
29 | ],
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/johanneshoppe/angular-date-value-accessor/issues"
33 | },
34 | "homepage": "https://github.com/johanneshoppe/angular-date-value-accessor",
35 | "peerDependencies": {
36 | "@angular/common": ">= 14.0.0",
37 | "@angular/core": ">= 14.0.0"
38 | },
39 | "dependencies": {
40 | "tslib": "^2.0.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/date-value-accessor.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { DateValueAccessor } from './date-value-accessor';
3 |
4 | @NgModule({
5 | imports: [DateValueAccessor],
6 | exports: [DateValueAccessor]
7 | })
8 | export class DateValueAccessorModule { }
9 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/date-value-accessor.reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { FormControl } from '@angular/forms';
3 |
4 | import { DateValueAccessor } from './date-value-accessor';
5 | import { Context, dispatchInputEvent, setupReactiveForms } from './spec-utils';
6 |
7 | @Component({
8 | template: `
9 |
`
12 | })
13 | export class TestFormComponent {
14 | testDate = new FormControl(new Date('2020-01-01'), { nonNullable: true }) // Create UTC Date
15 | }
16 |
17 | describe('DateValueAccessor (reactive forms)', () => {
18 |
19 | let c: Context = {};
20 | setupReactiveForms(c, TestFormComponent, DateValueAccessor);
21 |
22 | it('should reflect changes from the model to the input after init', () => {
23 | expect(c.inputElement.value).toBe('2020-01-01');
24 | });
25 |
26 | it('should reflect changes from the model to the input', () => {
27 | c.fixture.componentInstance.testDate.setValue(new Date('2021-11-15'));
28 | c.fixture.detectChanges();
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | });
31 |
32 | it('should reflect changes from the input to the model', () => {
33 | dispatchInputEvent(c.inputElement, '2020-12-31');
34 |
35 | expect(c.fixture.componentInstance.testDate.value).toEqual(jasmine.any(Date));
36 | expect(c.fixture.componentInstance.testDate.value).toEqual(new Date('2020-12-31'));
37 | expect(c.fixture.componentInstance.testDate.value.getUTCDate()).toBe(31);
38 | expect(c.fixture.componentInstance.testDate.value.getUTCMonth()).toBe(11);
39 | expect(c.fixture.componentInstance.testDate.value.getUTCFullYear()).toBe(2020);
40 | expect(c.fixture.componentInstance.testDate.value.getUTCHours()).toBe(0);
41 | expect(c.fixture.componentInstance.testDate.value.getUTCMinutes()).toBe(0);
42 | });
43 |
44 | it('should populate NULL for invalid dates', () => {
45 | dispatchInputEvent(c.inputElement, "NOT A DATE");
46 | expect(c.fixture.componentInstance.testDate.value).toBeNull();
47 | expect(c.inputElement.value).toBe('');
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/date-value-accessor.template.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { DateValueAccessor } from './date-value-accessor';
4 | import { Context, dispatchInputEvent, setupTemplateDrivenForms } from './spec-utils';
5 |
6 | @Component({
7 | template: `
8 | `
11 | })
12 | export class TestFormComponent {
13 | testDate = new Date('2020-01-01'); // Create UTC Date
14 | }
15 |
16 | describe('DateValueAccessor (template-driven forms)', () => {
17 |
18 | let c: Context = {};
19 | setupTemplateDrivenForms(c, TestFormComponent, DateValueAccessor);
20 |
21 | it('should reflect changes from the model to the input after init', () => {
22 | expect(c.inputElement.value).toBe('2020-01-01');
23 | });
24 |
25 | it('should reflect changes from the model to the input', done => {
26 | c.fixture.componentInstance.testDate = new Date('2021-11-15');
27 | c.fixture.detectChanges();
28 | c.fixture.whenStable().then(() => {
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | done();
31 | })
32 | });
33 |
34 | it('should reflect changes from the input to the model', () => {
35 | dispatchInputEvent(c.inputElement, '2020-12-31');
36 |
37 | expect(c.fixture.componentInstance.testDate).toEqual(jasmine.any(Date));
38 | expect(c.fixture.componentInstance.testDate).toEqual(new Date('2020-12-31'));
39 | expect(c.fixture.componentInstance.testDate.getUTCDate()).toBe(31);
40 | expect(c.fixture.componentInstance.testDate.getUTCMonth()).toBe(11);
41 | expect(c.fixture.componentInstance.testDate.getUTCFullYear()).toBe(2020);
42 | expect(c.fixture.componentInstance.testDate.getUTCHours()).toBe(0);
43 | expect(c.fixture.componentInstance.testDate.getUTCMinutes()).toBe(0);
44 | });
45 |
46 | it('should populate NULL for invalid dates', () => {
47 | dispatchInputEvent(c.inputElement, "NOT A DATE");
48 | expect(c.fixture.componentInstance.testDate).toBeNull();
49 | expect(c.inputElement.value).toBe('');
50 | });
51 | });
52 |
53 |
54 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/date-value-accessor.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, forwardRef, HostListener, Renderer2 } from '@angular/core';
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | /**
5 | * The accessor for writing a date object value and listening to changes on a date input element.
6 | *
7 | * ### Example
8 | * ``
9 | */
10 | @Directive({
11 | selector: '[useValueAsDate]',
12 | providers: [
13 | {
14 | provide: NG_VALUE_ACCESSOR,
15 | useExisting: forwardRef(() => DateValueAccessor),
16 | multi: true
17 | }
18 | ],
19 | standalone: true
20 | })
21 | export class DateValueAccessor implements ControlValueAccessor {
22 |
23 | @HostListener('input', ['$event.target.valueAsDate']) onChange = (_: any) => { };
24 | @HostListener('blur', []) onTouched = () => { };
25 |
26 | constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
27 |
28 | writeValue(date?: Date): void {
29 | this.renderer.setProperty(this.elementRef.nativeElement, 'valueAsDate', date);
30 | }
31 |
32 | registerOnChange(fn: (_: any) => void): void {
33 | this.onChange = fn;
34 | }
35 |
36 | registerOnTouched(fn: () => void): void {
37 | this.onTouched = fn;
38 | }
39 |
40 | setDisabledState(isDisabled: boolean): void {
41 | this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/default-value-accessor.template.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { Context, dispatchInputEvent, setupTemplateDrivenForms } from './spec-utils';
4 |
5 | @Component({
6 | template: `
7 | `
10 | })
11 | export class TestFormComponent {
12 | testDate: Date | string = new Date('2020-01-01'); // Create UTC Date
13 | }
14 |
15 | // just to describe the default behaviour af the inbuild DefaultValueAccessor
16 | describe('DefaultValueAccessor (template-driven forms)', () => {
17 |
18 | let c: Context = {};
19 | setupTemplateDrivenForms(c, TestFormComponent);
20 |
21 | it('should NOT reflect changes from the model to the input after init (when using dates)', () => {
22 | expect(c.fixture.componentInstance.testDate).toEqual(new Date('2020-01-01')); // nothing happens
23 | expect(c.inputElement.value).toBe('');
24 | });
25 |
26 | it('should reflect changes from the input to the model, BUT returns simple strings', () => {
27 | dispatchInputEvent(c.inputElement, '2020-12-31');
28 | expect(c.fixture.componentInstance.testDate).toEqual('2020-12-31');
29 | });
30 |
31 | // All our value accessors have a different behaviour here
32 | // by design, they return NULL instead of an empty string
33 | // Default implementation in Angular:
34 | //
35 | // const normalizedValue = value == null ? '' : value;
36 | //
37 | // see: https://github.com/angular/angular/blob/539d720fcde2ba5094224abc6e7380f9f6d4828f/packages/forms/src/directives/default_value_accessor.ts#L104
38 | //
39 | // Q: Why NULL and not UNDEFINED?
40 | // A: input.valueAsDate also returns NULL on broken input values
41 | it('IMPORTANT: should populate EMPTY STRING for invalid dates', () => {
42 | dispatchInputEvent(c.inputElement, "NOT A DATE");
43 | expect(c.fixture.componentInstance.testDate).toBe(''); // <-- !!!!
44 | expect(c.inputElement.value).toBe('');
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/iso-date-value-accessor.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IsoDateValueAccessor } from './iso-date-value-accessor';
3 |
4 | @NgModule({
5 | imports: [IsoDateValueAccessor],
6 | exports: [IsoDateValueAccessor]
7 | })
8 | export class IsoDateValueAccessorModule { }
9 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/iso-date-value-accessor.reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { FormControl } from '@angular/forms';
3 |
4 | import { IsoDateValueAccessor } from './iso-date-value-accessor';
5 | import { Context, dispatchInputEvent, setupReactiveForms } from './spec-utils';
6 |
7 | @Component({
8 | template: `
9 | `
12 | })
13 | export class TestFormComponent {
14 | testDate = new FormControl(new Date('2020-01-01').toISOString(), { nonNullable: true }); // Create UTC Date
15 | }
16 |
17 | describe('IsoDateValueAccessor (reactive forms)', () => {
18 |
19 | let c: Context = {};
20 | setupReactiveForms(c, TestFormComponent, IsoDateValueAccessor);
21 |
22 | it('should reflect changes from the model to the input after init', () => {
23 | expect(c.inputElement.value).toBe('2020-01-01');
24 | });
25 |
26 | it('should reflect changes from the model to the input', () => {
27 | c.fixture.componentInstance.testDate.setValue(new Date('2021-11-15').toISOString());
28 | c.fixture.detectChanges();
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | });
31 |
32 | it('should reflect changes from the input to the model', () => {
33 | dispatchInputEvent(c.inputElement, '2020-12-31');
34 | expect(c.fixture.componentInstance.testDate.value).toBe('2020-12-31T00:00:00.000Z');
35 | });
36 |
37 | it('should populate NULL for invalid dates', () => {
38 | dispatchInputEvent(c.inputElement, "NOT A DATE");
39 | expect(c.fixture.componentInstance.testDate.value).toBeNull();
40 | expect(c.inputElement.value).toBe('');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/iso-date-value-accessor.template.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { IsoDateValueAccessor } from './iso-date-value-accessor';
4 | import { Context, dispatchInputEvent, setupTemplateDrivenForms } from './spec-utils';
5 |
6 | @Component({
7 | template: `
8 | `
11 | })
12 | export class TestFormComponent {
13 | testDate = new Date('2020-01-01').toISOString(); // Create UTC Date
14 | }
15 |
16 | describe('IsoDateValueAccessor (template-driven forms)', () => {
17 |
18 | let c: Context = {};
19 | setupTemplateDrivenForms(c, TestFormComponent, IsoDateValueAccessor);
20 |
21 | it('should reflect changes from the model to the input after init', () => {
22 | expect(c.inputElement.value).toBe('2020-01-01');
23 | });
24 |
25 | it('should reflect changes from the model to the input', done => {
26 | c.fixture.componentInstance.testDate = new Date('2021-11-15').toISOString();
27 | c.fixture.detectChanges();
28 | c.fixture.whenStable().then(() => {
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | done();
31 | })
32 | });
33 |
34 | it('should reflect changes from the input to the model', () => {
35 | dispatchInputEvent(c.inputElement, '2020-12-31');
36 | expect(c.fixture.componentInstance.testDate).toBe('2020-12-31T00:00:00.000Z');
37 | });
38 |
39 | it('should populate NULL for invalid dates', () => {
40 | dispatchInputEvent(c.inputElement, "NOT A DATE");
41 | expect(c.fixture.componentInstance.testDate).toBeNull();
42 | expect(c.inputElement.value).toBe('');
43 | });
44 | });
45 |
46 |
47 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/iso-date-value-accessor.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, forwardRef, HostBinding, HostListener, Renderer2 } from '@angular/core';
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | /**
5 | * The accessor for writing an iso-formatted string value and listening to changes on a date input element.
6 | *
7 | * ### Example
8 | * ``
9 | */
10 | @Directive({
11 | selector: '[useValueAsIso]',
12 | providers: [
13 | {
14 | provide: NG_VALUE_ACCESSOR,
15 | useExisting: forwardRef(() => IsoDateValueAccessor),
16 | multi: true
17 | }
18 | ],
19 | standalone: true
20 | })
21 | export class IsoDateValueAccessor implements ControlValueAccessor {
22 |
23 | @HostListener('input', ['$event.target.valueAsDate']) onInput = (date?: Date) => {
24 | const isoString = date ? date.toISOString() : null;
25 | this.onChange(isoString);
26 | }
27 | onChange: any = () => {};
28 |
29 | @HostListener('blur', []) onTouched = () => { };
30 |
31 | constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
32 |
33 | writeValue(isoString?: string): void {
34 | const date = isoString ? new Date(isoString) : null;
35 | this.renderer.setProperty(this.elementRef.nativeElement, 'valueAsDate', date);
36 | }
37 |
38 | registerOnChange(fn: (_: any) => void): void {
39 | this.onChange = fn;
40 | }
41 |
42 | registerOnTouched(fn: any): void {
43 | this.onTouched = fn;
44 | }
45 |
46 | setDisabledState(isDisabled: boolean): void {
47 | this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-date-value-accessor.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { LocalDateValueAccessor } from './local-date-value-accessor';
3 |
4 | @NgModule({
5 | imports: [LocalDateValueAccessor],
6 | exports: [LocalDateValueAccessor]
7 | })
8 | export class LocalDateValueAccessorModule { }
9 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-date-value-accessor.reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { FormControl } from '@angular/forms';
3 |
4 | import { LocalDateValueAccessor } from './local-date-value-accessor';
5 | import { Context, dispatchInputEvent, setupReactiveForms } from './spec-utils';
6 |
7 | @Component({
8 | template: `
9 | `
12 | })
13 | export class TestFormComponent {
14 | testDate = new FormControl(new Date(2020, 11, 8), { nonNullable: true }); // Create LOCAL Date, HINT: Everything except the day is 0 based!
15 | }
16 |
17 | describe('LocalDateValueAccessor (reactive forms)', () => {
18 |
19 | let c: Context = {};
20 | setupReactiveForms(c, TestFormComponent, LocalDateValueAccessor);
21 |
22 | it('should reflect changes from the model to the input after init', () => {
23 | expect(c.inputElement.value).toBe('2020-12-08');
24 | });
25 |
26 | it('should reflect changes from the model to the input', () => {
27 | c.fixture.componentInstance.testDate.setValue(new Date(2021, 10, 15));
28 | c.fixture.detectChanges();
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | });
31 |
32 | it('should reflect changes from the input to the model', () => {
33 | dispatchInputEvent(c.inputElement, '2020-12-31');
34 |
35 | expect(c.fixture.componentInstance.testDate.value).toEqual(jasmine.any(Date));
36 | expect(c.fixture.componentInstance.testDate.value).toEqual(new Date(2020, 11, 31));
37 | expect(c.fixture.componentInstance.testDate.value.getDate()).toBe(31);
38 | expect(c.fixture.componentInstance.testDate.value.getMonth()).toBe(11);
39 | expect(c.fixture.componentInstance.testDate.value.getFullYear()).toBe(2020);
40 | expect(c.fixture.componentInstance.testDate.value.getHours()).toBe(0);
41 | expect(c.fixture.componentInstance.testDate.value.getMinutes()).toBe(0);
42 | });
43 |
44 | it('should populate NULL for invalid dates', () => {
45 | dispatchInputEvent(c.inputElement, "NOT A DATE");
46 | expect(c.fixture.componentInstance.testDate.value).toBeNull();
47 | expect(c.inputElement.value).toBe('');
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-date-value-accessor.template.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { waitForAsync } from '@angular/core/testing';
3 |
4 | import { LocalDateValueAccessor } from './local-date-value-accessor';
5 | import { Context, dispatchInputEvent, setupTemplateDrivenForms } from './spec-utils';
6 |
7 | @Component({
8 | template: `
9 | `
12 | })
13 | export class TestFormComponent {
14 | testDate: Date = new Date(2020, 11, 8); // Create LOCAL Date, HINT: Everything except the day is 0 based!
15 | }
16 |
17 | describe('LocalDateValueAccessor (template-driven forms)', () => {
18 |
19 | let c: Context = {};
20 | setupTemplateDrivenForms(c, TestFormComponent, LocalDateValueAccessor);
21 |
22 | it('should reflect changes from the model to the input after init', () => {
23 | expect(c.inputElement.value).toBe('2020-12-08');
24 | });
25 |
26 | it('should reflect changes from the model to the input', done => {
27 | c.fixture.componentInstance.testDate = new Date(2021, 10, 15);
28 | c.fixture.detectChanges();
29 | c.fixture.whenStable().then(() => {
30 | expect(c.inputElement.value).toBe('2021-11-15');
31 | done();
32 | })
33 | });
34 |
35 | it('should reflect changes from the input to the model', () => {
36 | dispatchInputEvent(c.inputElement, '2020-12-31');
37 |
38 | expect(c.fixture.componentInstance.testDate).toEqual(jasmine.any(Date));
39 | expect(c.fixture.componentInstance.testDate).toEqual(new Date(2020, 11, 31));
40 | expect(c.fixture.componentInstance.testDate.getDate()).toBe(31);
41 | expect(c.fixture.componentInstance.testDate.getMonth()).toBe(11);
42 | expect(c.fixture.componentInstance.testDate.getFullYear()).toBe(2020);
43 | expect(c.fixture.componentInstance.testDate.getHours()).toBe(0);
44 | expect(c.fixture.componentInstance.testDate.getMinutes()).toBe(0);
45 | });
46 |
47 | it('should populate NULL for invalid dates', () => {
48 | dispatchInputEvent(c.inputElement, "NOT A DATE");
49 | expect(c.fixture.componentInstance.testDate).toBeNull();
50 | expect(c.inputElement.value).toBe('');
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-date-value-accessor.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, forwardRef, HostListener, Renderer2 } from '@angular/core';
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | /**
5 | * The accessor for writing a value and listening to changes on a date input element in local time.
6 | *
7 | * ### Example
8 | * ``
9 | *
10 | * See also:
11 | * What is the correct way to set and get HTMLInputElement.valueAsDate using local Dates?
12 | * https://stackoverflow.com/a/53033442
13 | */
14 | @Directive({
15 | selector: '[useValueAsLocalDate]',
16 | providers: [
17 | {
18 | provide: NG_VALUE_ACCESSOR,
19 | useExisting: forwardRef(() => LocalDateValueAccessor),
20 | multi: true
21 | }
22 | ],
23 | standalone: true
24 | })
25 | export class LocalDateValueAccessor implements ControlValueAccessor {
26 |
27 | @HostListener('input', ['$event.target.valueAsDate']) onInput = (date?: Date) => {
28 | // convert to LOCAL Date, time is set to 00:00 in LOCAL time
29 | const localDate = date ? new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) : null;
30 | this.onChange(localDate);
31 | }
32 | onChange: any = () => {};
33 |
34 | @HostListener('blur', []) onTouched = () => { };
35 |
36 | constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
37 |
38 | writeValue(date?: Date): void {
39 | // convert to UTC Date, time is set to 00:00 in UTC time
40 | const utcDate = date ? new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) : null;
41 | this.renderer.setProperty(this.elementRef.nativeElement, 'valueAsDate', utcDate);
42 | }
43 |
44 | registerOnChange(fn: (_: any) => void): void {
45 | this.onChange = fn;
46 | }
47 |
48 | registerOnTouched(fn: () => void): void {
49 | this.onTouched = fn;
50 | }
51 |
52 | setDisabledState(isDisabled: boolean): void {
53 | this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-iso-date-value-accessor.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { LocalIsoDateValueAccessor } from './local-iso-date-value-accessor';
3 |
4 | @NgModule({
5 | imports: [LocalIsoDateValueAccessor],
6 | exports: [LocalIsoDateValueAccessor]
7 | })
8 | export class LocalIsoDateValueAccessorModule { }
9 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-iso-date-value-accessor.reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { FormControl } from '@angular/forms';
3 |
4 | import { LocalIsoDateValueAccessor } from './local-iso-date-value-accessor';
5 | import { Context, dispatchInputEvent, setupReactiveForms } from './spec-utils';
6 |
7 | @Component({
8 | template: `
9 | `
12 | })
13 | export class TestFormComponent {
14 | testDate = new FormControl(new Date(2020, 11, 8).toISOString(), { nonNullable: true }); // Create LOCAL Date, HINT: Everything except the day is 0 based!
15 | }
16 |
17 | describe('LocalIsoDateValueAccessor (reactive forms)', () => {
18 |
19 | let c: Context = {};
20 | setupReactiveForms(c, TestFormComponent, LocalIsoDateValueAccessor);
21 |
22 | it('should reflect changes from the model to the input after init', () => {
23 | expect(c.inputElement.value).toBe('2020-12-08');
24 | });
25 |
26 | it('should reflect changes from the model to the input', () => {
27 | c.fixture.componentInstance.testDate.setValue(new Date(2021, 10, 15).toISOString());
28 | c.fixture.detectChanges();
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | });
31 |
32 | it('should reflect changes from the input to the model', () => {
33 | dispatchInputEvent(c.inputElement, '2020-12-31');
34 | expect(c.fixture.componentInstance.testDate.value).toBe(new Date(2020, 11, 31).toISOString());
35 | });
36 |
37 | it('should populate NULL for invalid dates', () => {
38 | dispatchInputEvent(c.inputElement, "NOT A DATE");
39 | expect(c.fixture.componentInstance.testDate.value).toBeNull();
40 | expect(c.inputElement.value).toBe('');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-iso-date-value-accessor.template.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { LocalIsoDateValueAccessor } from './local-iso-date-value-accessor';
4 | import { Context, dispatchInputEvent, setupTemplateDrivenForms } from './spec-utils';
5 |
6 | @Component({
7 | template: `
8 | `
11 | })
12 | export class TestFormComponent {
13 | testDate = new Date(2020, 11, 8).toISOString(); // Create LOCAL Date, HINT: Everything except the day is 0 based!
14 | }
15 |
16 | describe('LocalIsoDateValueAccessor (template-driven forms)', () => {
17 |
18 | let c: Context = {};
19 | setupTemplateDrivenForms(c, TestFormComponent, LocalIsoDateValueAccessor);
20 |
21 | it('should reflect changes from the model to the input after init', () => {
22 | expect(c.inputElement.value).toBe('2020-12-08');
23 | });
24 |
25 | it('should reflect changes from the model to the input', done => {
26 | c.fixture.componentInstance.testDate = new Date(2021, 10, 15).toISOString();
27 | c.fixture.detectChanges();
28 | c.fixture.whenStable().then(() => {
29 | expect(c.inputElement.value).toBe('2021-11-15');
30 | done();
31 | })
32 | });
33 |
34 | it('should reflect changes from the input to the model', () => {
35 | dispatchInputEvent(c.inputElement, '2020-12-31');
36 | expect(c.fixture.componentInstance.testDate).toBe(new Date(2020, 11, 31).toISOString());
37 | });
38 |
39 | it('should populate NULL for invalid dates', () => {
40 | dispatchInputEvent(c.inputElement, "NOT A DATE");
41 | expect(c.fixture.componentInstance.testDate).toBeNull();
42 | expect(c.inputElement.value).toBe('');
43 | });
44 | });
45 |
46 |
47 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/local-iso-date-value-accessor.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, forwardRef, HostBinding, HostListener, Renderer2 } from '@angular/core';
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | /**
5 | * The accessor for writing an iso-formatted string and listening to changes on a date input element.
6 | *
7 | * ### Example
8 | * ``
9 | *
10 | * See also:
11 | * What is the correct way to set and get HTMLInputElement.valueAsDate using local Dates?
12 | * https://stackoverflow.com/a/53033442
13 | */
14 | @Directive({
15 | selector: '[useValueAsLocalIso]',
16 | providers: [
17 | {
18 | provide: NG_VALUE_ACCESSOR,
19 | useExisting: forwardRef(() => LocalIsoDateValueAccessor),
20 | multi: true
21 | }
22 | ],
23 | standalone: true
24 | })
25 | export class LocalIsoDateValueAccessor implements ControlValueAccessor {
26 |
27 | @HostListener('input', ['$event.target.valueAsDate']) onInput = (date?: Date) => {
28 | // convert to LOCAL Date, time is set to 00:00 in LOCAL time
29 | const localDate = date ? new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) : null;
30 | const isoString = localDate ? localDate.toISOString() : null;
31 | this.onChange(isoString);
32 | }
33 | onChange: any = () => {};
34 |
35 | @HostListener('blur', []) onTouched = () => { };
36 |
37 | constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
38 |
39 | writeValue(isoString?: string): void {
40 | const date = isoString ? new Date(isoString) : null;
41 | // convert to UTC Date, time is set to 00:00 in UTC time
42 | const utcDate = date ? new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) : null;
43 | this.renderer.setProperty(this.elementRef.nativeElement, 'valueAsDate', utcDate);
44 | }
45 |
46 | registerOnChange(fn: (_: any) => void): void {
47 | this.onChange = fn;
48 | }
49 |
50 | registerOnTouched(fn: () => void): void {
51 | this.onTouched = fn;
52 | }
53 |
54 | setDisabledState(isDisabled: boolean): void {
55 | this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/lib/spec-utils.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@angular/core';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 |
5 |
6 | export function dispatchInputEvent(inputElement: HTMLInputElement, text: string): void {
7 | inputElement.value = text;
8 | inputElement.dispatchEvent(new Event('input'));
9 | }
10 |
11 | export interface Context {
12 | fixture?: ComponentFixture;
13 | inputElement?: HTMLInputElement;
14 | }
15 |
16 | export function setupTemplateDrivenForms(
17 | context: Context,
18 | testComponent: Type,
19 | acessor?: TAcess) {
20 |
21 | beforeEach(waitForAsync(() => {
22 | TestBed.configureTestingModule({
23 | declarations: [testComponent],
24 | imports: [
25 | FormsModule,
26 | ...(acessor ? [acessor] : [])
27 | ],
28 | teardown: { destroyAfterEach: false }
29 | })
30 | .compileComponents();
31 | }));
32 |
33 | beforeEach(() => {
34 | context.fixture = TestBed.createComponent(testComponent);
35 | });
36 |
37 | beforeEach(waitForAsync(() => {
38 | // https://stackoverflow.com/questions/39582707/updating-input-html-field-from-within-an-angular-2-test
39 | context.fixture.detectChanges();
40 | context.fixture.whenStable();
41 | }));
42 |
43 | beforeEach(() => context.inputElement = context.fixture.nativeElement.querySelector('input'));
44 | }
45 |
46 | export function setupReactiveForms(
47 | context: Context,
48 | testComponent: Type,
49 | acessor?: TAcess) {
50 |
51 | beforeEach(waitForAsync(() => {
52 | TestBed.configureTestingModule({
53 | declarations: [testComponent],
54 | imports: [
55 | ReactiveFormsModule,
56 | ...(acessor ? [acessor] : [])
57 | ],
58 | teardown: { destroyAfterEach: false }
59 | })
60 | .compileComponents();
61 | }));
62 |
63 | beforeEach(() => {
64 | context.fixture = TestBed.createComponent(testComponent);
65 | context.fixture.detectChanges();
66 | });
67 |
68 | beforeEach(() => context.inputElement = context.fixture.nativeElement.querySelector('input'));
69 | }
70 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of the date-value-accessor
3 | */
4 |
5 | export * from './lib/date-value-accessor';
6 | export * from './lib/date-value-accessor.module';
7 | export * from './lib/local-date-value-accessor';
8 | export * from './lib/local-date-value-accessor.module';
9 |
10 | export * from './lib/iso-date-value-accessor';
11 | export * from './lib/iso-date-value-accessor.module';
12 | export * from './lib/local-iso-date-value-accessor';
13 | export * from './lib/local-iso-date-value-accessor.module';
14 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: {
12 | context(path: string, deep?: boolean, filter?: RegExp): {
13 | keys(): string[];
14 | (id: string): T;
15 | };
16 | };
17 |
18 | // First, initialize the Angular testing environment.
19 | getTestBed().initTestEnvironment(
20 | BrowserDynamicTestingModule,
21 | platformBrowserDynamicTesting()
22 | );
23 | // Then we find all the tests.
24 | const context = require.context('./', true, /\.spec\.ts$/);
25 | // And load the modules.
26 | context.keys().map(context);
27 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/lib",
6 | "target": "es2020",
7 | "declaration": true,
8 | "declarationMap": true,
9 | "inlineSources": true,
10 | "types": [],
11 | "lib": [
12 | "dom",
13 | "es2018"
14 | ]
15 | },
16 | "angularCompilerOptions": {
17 | "skipTemplateCodegen": true,
18 | "strictMetadataEmit": true,
19 | "enableResourceInlining": true
20 | },
21 | "exclude": [
22 | "src/test.ts",
23 | "**/*.spec.ts"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/workspace/projects/date-value-accessor/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "lib",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | true,
12 | "element",
13 | "lib",
14 | "kebab-case"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/workspace/projects/demo/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 | IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
18 | IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
19 |
--------------------------------------------------------------------------------
/workspace/projects/demo/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage/demo'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/workspace/projects/demo/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/app/app.component.css
--------------------------------------------------------------------------------
/workspace/projects/demo/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
DateValueAccessor Demo
2 |
3 |
4 | This project knows a number of different value accessors.
5 |
6 |
7 |
8 |
If you want to use date objects, select the first option.
9 |
If you want to work with ISO-formatted strings instead, select the second option.
7 | The orginal DateValueAccessor operates in UTC (Coordinated Universal Time).
8 | The HTML date input will use the UTC representation of a given Date Object.
9 | When you select a date it will output an UTC date with the time set to 00:00 (UTC).
10 |
11 |
12 | If you are unsure what to use, use the LocalDateValueAccessor and not the DateValueAccessor.
13 | Most users will expect the input field to correlate to their local clock.
14 |
15 |
7 | The DefaultValueAccessor from Angular will not work as expected.
8 | The HTML date input will not display the given Date Object – it's empty after load.
9 | When you select a date it will output as a simple string. Please note the typeof(releaseDate) after setting a value.
10 | This simple string contains only a date and no time.
11 |
7 | This directive gets and sets ISO 8601 formatted date strings in HTML date inputs.
8 | The handling of the dates is the same as for the DateValueAccessor.
9 |
10 | The IsoDateValueAccessor operates in UTC (Coordinated Universal Time).
11 | The HTML date input will use the UTC representation of a given ISO 8601 formatted date string.
12 | When you select a date it will output an ISO-formatted string with the time set to 00:00 (UTC).
13 |
7 | The improved LocalDateValueAccessor operates in your Local Time.
8 | The HTML date input will use the Local Time representation of a given the Date Object.
9 | When you select a date it will output a Local Date with the time set to 00:00 (Local Time).
10 |
7 | This directive gets and sets ISO 8601 formatted date strings in HTML date inputs.
8 | The handling of the dates is the same as for the LocalDateValueAccessor.
9 |
10 | The LocalIsoDateValueAccessor operates in your Local Time.
11 | The HTML date input will use the Local Time representation of a given ISO 8601 formatted date string.
12 | When you select a date it will output an ISO-formatted string with a time that equals to 00:00 (Local Time).
13 |
14 | Note: The timezone of the outputted string is always zero UTC offset, as denoted by the suffix "Z".
15 |
`
16 | })
17 | export class ExplanationLocalIsoDateValueAccessorComponent {
18 | }
19 |
--------------------------------------------------------------------------------
/workspace/projects/demo/src/app/shared/release-with-iso-string.ts:
--------------------------------------------------------------------------------
1 | export class ReleaseWithIsoString {
2 |
3 | constructor(
4 | public version: string,
5 | public releaseDate: string) {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/workspace/projects/demo/src/app/shared/release.ts:
--------------------------------------------------------------------------------
1 | export class Release {
2 |
3 | constructor(
4 | public version: string,
5 | public releaseDate: Date) {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/.gitkeep
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/ie11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/ie11.png
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/reactive-does-not-work.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/reactive-does-not-work.gif
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/reactive-works-local.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/reactive-works-local.gif
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/reactive-works.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/reactive-works.gif
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/screenshot.png
--------------------------------------------------------------------------------
/workspace/projects/demo/src/assets/screenshot_simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/assets/screenshot_simple.png
--------------------------------------------------------------------------------
/workspace/projects/demo/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/workspace/projects/demo/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/workspace/projects/demo/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohannesHoppe/angular-date-value-accessor/8a4caeb45bdacc45c3b523106f6ac42dc0389ca2/workspace/projects/demo/src/favicon.ico
--------------------------------------------------------------------------------
/workspace/projects/demo/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular: DateValueAccessor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |