diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 37876749657..bb8f41e03c1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -153,6 +153,33 @@ If the bug fix or new feature development requires changes to released public AP 2. Add a `BREAKING CHANGE:` section to the commit message body or footer. See https://www.conventionalcommits.org 3. Check if the change can be migrated by `ng update` schematics and add to the project migrations. See [Update Migrations wiki](https://github.com/IgniteUI/igniteui-angular/wiki/Update-Migrations) for available functionality and instructions. +## Deprecating selectors +When deprecating selectors the following code should be placed inside `OnInit` method of the class the selector belongs to: +` +import { isDevMode } from '@angular/core'; +... +constructor(..., private element: ElementRef) {} +... +if (isDevMode() && this.element.nativeElement.tagName === 'your deprecated selector in upper case') { + console.log('your deprecation message'); +} +` + +Write migrations. + +## Deprecating methods +When a method is deprecated a few steps have to be done: +1. Add deprecation warning message by decorating the method with `@DeprecateMethod` decorator from `deprecateDecorators.ts` file. +2. Ensure that the deprecated method is no longer used in IgniteUI for Angular codebase, samples and documentation snippets. +3. Write migrations. + +## Deprecating class properties +When a class property is deprecated a few steps have to be done: +1. Add deprecation warning message by decorating the property with `@DeprecateProperty` decorator from `deprecateDecorators.ts` file. +2. Ensure that the deprecated property is no longer used in IgniteUI for Angular codebase, samples and documentation snippets. +3. Write migrations. + +NOTE: TypeScript disallows decorating both the get and set accessor for a single member. Instead, all decorators for the member must be applied to the first accessor specified in document order. This is because decorators apply to a Property Descriptor, which combines both the get and set accessor, not each declaration separately. # Testing a PR In order to test a pull request that is awaiting test, perform the following actions. diff --git a/projects/igniteui-angular/src/lib/core/deprecateDecorators.ts b/projects/igniteui-angular/src/lib/core/deprecateDecorators.ts index 8273d65ec9e..c83b071d76d 100644 --- a/projects/igniteui-angular/src/lib/core/deprecateDecorators.ts +++ b/projects/igniteui-angular/src/lib/core/deprecateDecorators.ts @@ -1,27 +1,96 @@ +import { isDevMode } from '@angular/core'; /** * @hidden */ -export function DeprecateClass(message: string): ClassDecorator { - return (constructor: any) => { - console.warn(constructor.name + ': ' + message); +export function DeprecateMethod(message: string): MethodDecorator { + let isMessageShown = false; + + return function (target: any, key: string, descriptor: PropertyDescriptor) { + if (descriptor && descriptor.value) { + const originalMethod = descriptor.value; + + descriptor.value = function () { + const targetName = typeof target === 'function' ? target.name : target.constructor.name; + isMessageShown = showMessage(`${targetName}.${key}: ${message}`, isMessageShown); + + return originalMethod.call(this, arguments); + }; + + return descriptor; + } }; } /** * @hidden */ -export function DeprecateMethod(message: string): MethodDecorator { - return (constructor: any) => { - console.warn(constructor.constructor.name + ': ' + message); +export function DeprecateProperty(message: string): PropertyDecorator { + return function(target: any, key: string) { + let isMessageShown = false; + const messageToDisplay = `${target.constructor.name}.${key}: ${message}`; + + // if the target already has the property defined + const originalDescriptor = Object.getOwnPropertyDescriptor(target, key); + if (originalDescriptor) { + let getter, setter; + getter = originalDescriptor.get; + setter = originalDescriptor.set; + + if (getter) { + originalDescriptor.get = function() { + isMessageShown = showMessage(messageToDisplay, isMessageShown); + return getter.call(this); + }; + } + + if (setter) { + originalDescriptor.set = function (value) { + isMessageShown = showMessage(messageToDisplay, isMessageShown); + setter.call(this, value); + }; + } + + return originalDescriptor; + } + + // the target doesn't contain a descriptor for that property, so create one + // use backing field to set/get the value of the property to ensure there won't be infinite recursive calls + const newKey = generateUniqueKey(target, key); + return Object.defineProperty(target, key, { + configurable: true, + enumerable: true, + set: function(value) { + isMessageShown = showMessage(messageToDisplay, isMessageShown); + this[newKey] = value; + }, + get: function() { + isMessageShown = showMessage(messageToDisplay, isMessageShown); + return this[newKey]; + } + }); }; } /** * @hidden */ -export function DeprecateProperty(message: string): PropertyDecorator { - return (constructor: any) => { - console.warn(constructor.constructor.name + ': ' + message); - }; +function generateUniqueKey(target: any, key: string): string { + let newKey = '_' + key; + while (target.hasOwnProperty(newKey)) { + newKey = '_' + newKey; + } + + return newKey; +} + +/** + * @hidden + */ +function showMessage(message: string, isMessageShown: boolean): boolean { + if (!isMessageShown && isDevMode()) { + console.warn(message); + } + + return true; } diff --git a/projects/igniteui-angular/src/lib/date-picker/README.md b/projects/igniteui-angular/src/lib/date-picker/README.md index 9e57586428f..b2bf055892d 100644 --- a/projects/igniteui-angular/src/lib/date-picker/README.md +++ b/projects/igniteui-angular/src/lib/date-picker/README.md @@ -61,14 +61,14 @@ The DatePicker also supports binding through `ngModel` if two-way date-bind is n The DatePicker input group could be retemplated. ```html - + - + ``` # API diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts index a91c249abd7..cc318ee9e8b 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts @@ -435,14 +435,14 @@ export class IgxDatePickerNgModelComponent { @Component({ template: ` - + - + ` }) export class IgxDatePickerRetemplatedComponent {} diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts index f1ac34d5ace..bd7fa901f4c 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts @@ -16,7 +16,8 @@ import { HostListener, ElementRef, TemplateRef, - Directive + Directive, + isDevMode } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { @@ -33,7 +34,6 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive'; import { OverlaySettings } from '../services'; -import { DeprecateClass } from '../core/deprecateDecorators'; import { DateRangeDescriptor } from '../core/dates/dateRange'; import { EditorProvider } from '../core/edit-provider'; @@ -77,7 +77,6 @@ let NEXT_ID = 0; styles: [':host {display: block;}'], templateUrl: 'date-picker.component.html' }) -@DeprecateClass('\'igx-datePicker\' selector is deprecated. Use \'igx-date-picker\' selector instead.') export class IgxDatePickerComponent implements ControlValueAccessor, EditorProvider, OnInit, OnDestroy { /** *An @Input property that sets the value of `id` attribute. If not provided it will be automatically generated. @@ -446,7 +445,7 @@ export class IgxDatePickerComponent implements ControlValueAccessor, EditorProvi @ViewChild(IgxInputDirective) protected input: IgxInputDirective; - constructor(private resolver: ComponentFactoryResolver) { } + constructor(private resolver: ComponentFactoryResolver, private element: ElementRef) { } /** *Method that sets the selected date. @@ -486,6 +485,10 @@ export class IgxDatePickerComponent implements ControlValueAccessor, EditorProvi public ngOnInit(): void { this.alert.onOpen.pipe(takeUntil(this.destroy$)).subscribe((ev) => this._focusCalendarDate()); this.alert.toggleRef.onClosed.pipe(takeUntil(this.destroy$)).subscribe((ev) => this.handleDialogCloseAction()); + + if (isDevMode() && this.element.nativeElement.tagName === 'IGX-DATEPICKER') { + console.warn('IgxDatePickerComponent: \'igx-datePicker\' selector is deprecated. Use \'igx-date-picker\' selector instead.'); + } } /** diff --git a/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts b/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts index a497bc06804..33a769304c1 100644 --- a/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/toggle/toggle.directive.ts @@ -20,6 +20,7 @@ import { filter, takeUntil } from 'rxjs/operators'; import { Subscription, Subject, MonoTypeOperatorFunction } from 'rxjs'; import { OverlayCancelableEventArgs } from '../../services/overlay/utilities'; import { CancelableEventArgs } from '../../core/utils'; +import { DeprecateProperty } from '../../core/deprecateDecorators'; @Directive({ exportAs: 'toggle', @@ -323,6 +324,8 @@ export class IgxToggleActionDirective implements OnInit { * let closesOnOutsideClick = this.toggle.closeOnOutsideClick; * ``` */ + @Input() + @DeprecateProperty(`igxToggleAction 'closeOnOutsideClick' input is deprecated. Use 'overlaySettings' input object instead.`) public get closeOnOutsideClick(): boolean { return this._closeOnOutsideClick; } @@ -332,9 +335,7 @@ export class IgxToggleActionDirective implements OnInit { *
* ``` */ - @Input() public set closeOnOutsideClick(v: boolean) { - console.warn(`igxToggleAction 'closeOnOutsideClick' input is deprecated. Use 'overlaySettings' input object instead.`); this._closeOnOutsideClick = v; } diff --git a/projects/igniteui-angular/src/lib/grids/cell.component.html b/projects/igniteui-angular/src/lib/grids/cell.component.html index b24af170912..1ec199418fe 100644 --- a/projects/igniteui-angular/src/lib/grids/cell.component.html +++ b/projects/igniteui-angular/src/lib/grids/cell.component.html @@ -22,7 +22,7 @@ - + diff --git a/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering-row.component.html b/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering-row.component.html index 1a1d212c20c..59a32876a66 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering-row.component.html @@ -36,7 +36,7 @@ - + - + diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts index fbcecb7fbaf..db3a35a5616 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts @@ -302,7 +302,7 @@ describe('IgxGrid - Cell component', () => { await wait(); expect(cell.inEditMode).toBe(true); - const datePicker = cellDomDate.query(By.css('igx-datepicker')).componentInstance; + const datePicker = cellDomDate.query(By.css('igx-date-picker')).componentInstance; expect(datePicker).toBeDefined(); datePicker.selectDate(selectedDate); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html index 785ee533a03..3a6426104ed 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.html @@ -22,7 +22,7 @@ - + diff --git a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts index 26786ce325e..7c850919898 100644 --- a/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts +++ b/projects/igniteui-angular/src/lib/tabbar/tabbar.component.ts @@ -21,7 +21,6 @@ import { ViewChildren } from '@angular/core'; import { IgxBadgeModule } from '../badge/badge.component'; -import { DeprecateClass } from '../core/deprecateDecorators'; import { IgxIconModule } from '../icon/index'; export interface ISelectTabEventArgs { diff --git a/src/app/date-picker/date-picker.sample.html b/src/app/date-picker/date-picker.sample.html index da562e90135..b0de6d16153 100644 --- a/src/app/date-picker/date-picker.sample.html +++ b/src/app/date-picker/date-picker.sample.html @@ -5,7 +5,7 @@

Default Date Picker.

Detailed description to be added.

- +
@@ -27,7 +27,7 @@

Date Picker with passed date and custom formatter.

Date Picker with retemplated input group.

Detailed description to be added.

- + @@ -35,7 +35,7 @@

Date Picker with retemplated input group.

-
+