Skip to content

Commit

Permalink
feat(ripples): support updating global ripple options at runtime (#14705
Browse files Browse the repository at this point in the history
)

* feat(ripples): support updating global ripple options at runtime

* Allows updating any global ripple option at runtime. This makes it possible for developers to disable ripples at runtime.

Closes #9729

* fixup! feat(ripples): support updating global ripple options at runtime

Address feedback
  • Loading branch information
devversion authored and vivian-hu-zz committed Jan 16, 2019
1 parent f6d0ea8 commit 4f755cf
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 96 deletions.
3 changes: 3 additions & 0 deletions src/dev-app/dev-app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MAT_RIPPLE_GLOBAL_OPTIONS} from '@angular/material';
import {ExampleModule} from '@angular/material-examples';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
Expand Down Expand Up @@ -50,6 +51,7 @@ import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
import {ProgressSpinnerDemo} from './progress-spinner/progress-spinner-demo';
import {RadioDemo} from './radio/radio-demo';
import {RippleDemo} from './ripple/ripple-demo';
import {DevAppRippleOptions} from './ripple/ripple-options';
import {DEV_APP_ROUTES} from './routes';
import {ScreenTypeDemo} from './screen-type/screen-type-demo';
import {SelectDemo} from './select/select-demo';
Expand Down Expand Up @@ -140,6 +142,7 @@ import {VirtualScrollDemo} from './virtual-scroll/virtual-scroll-demo';
],
providers: [
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: DevAppRippleOptions},
],
entryComponents: [
ContentElementDialog,
Expand Down
3 changes: 3 additions & 0 deletions src/dev-app/dev-app.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ <h1>Angular Material Demos</h1>
<mat-icon>fullscreen</mat-icon>
</button>
<button mat-button (click)="toggleTheme()">{{dark ? 'Light' : 'Dark'}} theme</button>
<button mat-button (click)="rippleOptions.disabled = !rippleOptions.disabled">
{{rippleOptions.disabled ? 'Enable' : 'Disable'}} ripples
</button>
<button mat-button (click)="root.dir = (root.dir === 'rtl' ? 'ltr' : 'rtl')"
title="Toggle between RTL and LTR">
{{root.dir.toUpperCase()}}
Expand Down
4 changes: 3 additions & 1 deletion src/dev-app/dev-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {OverlayContainer} from '@angular/cdk/overlay';
import {Component, ElementRef, ViewEncapsulation} from '@angular/core';
import {DevAppRippleOptions} from './ripple/ripple-options';

/** Root component for the dev-app demos. */
@Component({
Expand Down Expand Up @@ -69,7 +70,8 @@ export class DevAppComponent {

constructor(
private _element: ElementRef<HTMLElement>,
private _overlayContainer: OverlayContainer) {}
private _overlayContainer: OverlayContainer,
public rippleOptions: DevAppRippleOptions) {}

toggleFullscreen() {
// Cast to `any`, because the typings don't include the browser-prefixed methods.
Expand Down
21 changes: 21 additions & 0 deletions src/dev-app/ripple/ripple-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Injectable} from '@angular/core';
import {RippleGlobalOptions} from '@angular/material';

/**
* Global ripple options for the dev-app. The ripple options are used as a class
* so that the global options can be changed at runtime.
*/
@Injectable({providedIn: 'root'})
export class DevAppRippleOptions implements RippleGlobalOptions {

/** Whether ripples should be disabled */
disabled: boolean = false;
}
1 change: 1 addition & 0 deletions src/lib/chips/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ ng_test_library(
"@angular//packages/platform-browser/animations",
"//src/cdk/a11y",
"//src/cdk/bidi",
"//src/lib/core",
"//src/cdk/keycodes",
"//src/cdk/platform",
"//src/cdk/testing",
Expand Down
19 changes: 15 additions & 4 deletions src/lib/chips/chip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {BACKSPACE, DELETE, SPACE} from '@angular/cdk/keycodes';
import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
import {Component, DebugElement} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core';
import {By} from '@angular/platform-browser';
import {Subject} from 'rxjs';
import {MatChip, MatChipEvent, MatChipSelectionChange, MatChipsModule} from './index';
Expand All @@ -13,19 +14,22 @@ describe('Chips', () => {
let chipDebugElement: DebugElement;
let chipNativeElement: HTMLElement;
let chipInstance: MatChip;
let globalRippleOptions: RippleGlobalOptions;

let dir = 'ltr';

beforeEach(async(() => {
globalRippleOptions = {};
TestBed.configureTestingModule({
imports: [MatChipsModule],
declarations: [BasicChip, SingleChip],
providers: [{
provide: Directionality, useFactory: () => ({
providers: [
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions},
{provide: Directionality, useFactory: () => ({
value: dir,
change: new Subject()
})
}]
})},
]
});

TestBed.compileComponents();
Expand Down Expand Up @@ -203,6 +207,13 @@ describe('Chips', () => {
subscription.unsubscribe();
});

it('should be able to disable ripples through ripple global options at runtime', () => {
expect(chipInstance.rippleDisabled).toBe(false, 'Expected chip ripples to be enabled.');

globalRippleOptions.disabled = true;

expect(chipInstance.rippleDisabled).toBe(true, 'Expected chip ripples to be disabled.');
});
});

describe('keyboard behavior', () => {
Expand Down
24 changes: 8 additions & 16 deletions src/lib/chips/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,20 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
/** Reference to the RippleRenderer for the chip. */
private _chipRipple: RippleRenderer;

/** Whether the ripples are globally disabled through the RippleGlobalOptions */
private _ripplesGloballyDisabled = false;

/**
* Ripple configuration for ripples that are launched on pointer down.
* Ripple configuration for ripples that are launched on pointer down. The ripple config
* is set to the global ripple options since we don't have any configurable options for
* the chip ripples.
* @docs-private
*/
rippleConfig: RippleConfig = {};
rippleConfig: RippleConfig & RippleGlobalOptions;

/**
* Whether ripples are disabled on interaction
* @docs-private
*/
get rippleDisabled(): boolean {
return this.disabled || this.disableRipple || this._ripplesGloballyDisabled;
return this.disabled || this.disableRipple || !!this.rippleConfig.disabled;
}

/** Whether the chip has focus. */
Expand Down Expand Up @@ -225,22 +224,15 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
constructor(public _elementRef: ElementRef,
private _ngZone: NgZone,
platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS)
globalRippleOptions: RippleGlobalOptions | null) {
super(_elementRef);

this._addHostClassName();

this._chipRipple = new RippleRenderer(this, _ngZone, _elementRef, platform);
this._chipRipple.setupTriggerEvents(_elementRef.nativeElement);

if (globalOptions) {
// TODO(paul): Do not copy each option manually. Allow dynamic global option changes: #9729
this._ripplesGloballyDisabled = !!globalOptions.disabled;
this.rippleConfig = {
animation: globalOptions.animation,
terminateOnPointerUp: globalOptions.terminateOnPointerUp,
};
}
this.rippleConfig = globalRippleOptions || {};
}

_addHostClassName() {
Expand Down
52 changes: 46 additions & 6 deletions src/lib/core/ripple/ripple.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ and calling its `launch` method.
### Ripple trigger

By default ripples will fade in on interaction with the directive's host element.
In some situations, developers may want to show ripples on interaction with *some other* element,
In some situations, developers may want to show ripples on interaction with *some other* element,
but still want to have the ripples placed in another location. This can be done by specifying
the `matRippleTrigger` option that expects a reference to an `HTMLElement`.

Expand All @@ -29,7 +29,7 @@ the `matRippleTrigger` option that expects a reference to an `HTMLElement`.
<div matRipple [matRippleTrigger]="trigger" class="my-ripple-container">
<!-- This is the ripple container, but not the trigger element for ripples. -->
</div>

<div #trigger></div>
</div>
```
Expand All @@ -43,14 +43,14 @@ class MyComponent {

/** Reference to the directive instance of the ripple. */
@ViewChild(MatRipple) ripple: MatRipple;

/** Shows a centered and persistent ripple. */
launchRipple() {
const rippleRef = this.ripple.launch({
persistent: true,
centered: true
});

// Fade out the ripple later.
rippleRef.fadeOut();
}
Expand Down Expand Up @@ -91,7 +91,7 @@ const globalRippleConfig: RippleGlobalOptions = {

@NgModule({
providers: [
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useValue: globalRippleConfig}
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useValue: globalRippleConfig}
]
})
```
Expand All @@ -100,7 +100,7 @@ All available global options can be seen in the `RippleGlobalOptions` interface.

### Disabling animation

The animation of ripples can be disabled by using the `animation` global option. If the
The animation of ripples can be disabled by using the `animation` global option. If the
`enterDuration` and `exitDuration` is being set to `0`, ripples will just appear without any
animation.

Expand Down Expand Up @@ -139,3 +139,43 @@ const globalRippleConfig: RippleGlobalOptions = {
terminateOnPointerUp: true
};
```

### Updating global options at runtime

To change global ripple options at runtime, just inject the `MAT_RIPPLE_GLOBAL_OPTIONS`
provider and update the desired options.

There are various ways of injecting the global options. In order to make it easier to
inject and update options at runtime, it's recommended to create a service that implements
the `RippleGlobalOptions` interface.

```ts
@Injectable({providedIn: 'root'})
export class AppGlobalRippleOptions implements RippleGlobalOptions {
/** Whether ripples should be disabled globally. */
disabled: boolean = false;
}
```

```ts
@NgModule({
providers: [
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: AppGlobalRippleOptions},
]
})
export class MyModule {...}
```

Now that the global ripple options are set to a service we can inject, the service can be
used update any global ripple option at runtime.

```ts
@Component(...)
export class MyComponent {
constructor(private _appRippleOptions: AppGlobalRippleOptions) {}

disableRipples() {
this._appRippleOptions.disabled = true;
}
}
```
2 changes: 1 addition & 1 deletion src/lib/core/ripple/ripple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
constructor(private _elementRef: ElementRef<HTMLElement>,
ngZone: NgZone,
platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions?: RippleGlobalOptions,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) {

this._globalOptions = globalOptions || {};
Expand Down
Loading

0 comments on commit 4f755cf

Please sign in to comment.