-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Angular: Utilities, Patterns, Memo #165
Comments
Touch Events and NgZoneHerleitung: property binding, event binding, banana-in-the-box-syntax als Reminder Refresher/Reminder: event bubbling from children to parent -> triggers change detection Mit zone.js und NgZone: jedes DOM event fragt eine Change Detection an Spezialfall Touch Gesten: eine Geste besteht aus mehreren Events, eine Geste sollte eine Change Detection anfragen. Jedoch nicht jedes Event. Fallstricke: @Output() fooChange = new EventEmitter();
/** WATCH OUT: this method is not running in NgZone! */
onTouchMove() {
this.fooChange.next(); // <-- target of event binding would also run outside NgZone
} ===> /** WATCH OUT: this method is not running in NgZone! */
onTouchMove() {
this.ngZone.run(() => {
this.fooChange.next(); // <-- target of event binding will execute in NgZone (as expected)
});
} ngOnDestroy() {
this.removeListener();
} constructor(
private _cdr: ChangeDetectorRef,
public _sliderRef: ElementRef,
private ngZone: NgZone,
private renderer: Renderer2
) {
const nativeElement = this._sliderRef.nativeElement;
this.ngZone.runOutsideAngular(() => {
this.hammerSliderEl = new Hammer(nativeElement);
this.hammerSliderEl.on('panmove', guardOutsideAngular(event => this.onPanMove(event)));
});
}
ngOnDestroy() {
if (this.hammerSliderEl) {
// Gracefully clean up DOM Event listeners that were registed by hammer.js
this.hammerSliderEl.off('panmove');
this.hammerSliderEl.stop(true);
this.hammerSliderEl.destroy();
}
}
onPanMove($event) {
if (/* … magic expression … */) {
/* … only internal changes, no rendering needed … */)
} else {
this._cdr.detectChanges();
}
} // Run in ngZone because it should update slider values outside component
this.unregisterListeners = [
this.renderer.listen(nativeElement, 'touchstart', event => this._onTouchStart(event)),
this.renderer.listen(nativeElement, 'mousedown', event => this._onClick(event))
];
if (this.unregisterListeners) {
this.unregisterListeners.forEach(fn => fn());
} |
An Approach for Theming With Shadow DOM And Web ComponentsIdea: in Shadow DOM environments, let's go for CSS Variables! The component gives hooks how it can be styled, the container app decides what it looks like. https://developers.google.com/web/fundamentals/web-components/shadowdom#stylefromoutside @function my-theme-color($key: "primary”) {
// Returns the simple CSS variable expression `var(--primary)`
@return var(--#{$key});
}
@function my-theme-color($key: "primary”, $level: 0, $opacity: 0) {
// TODO: return HSLA calculation w/ CSS variable expression
} Herleitung:
Idee: CSS Variablen als "style hooks" https://developers.google.com/web/fundamentals/web-components/shadowdom#stylefromoutside @Component({
selector: 'my-themable-component',
template: `<div class="theme-hook"></div>`,
styles: [`
.theme-hook {
background-color: var(--color-palette-background-light);
color: var(--color-palette-text-dark);
}
`]
})
export class MyThemableComponent {
/* .. */
} Global Stylesheet :root {
--color-palette-background-light: #fff;
--color-palette-text-dark: #333;
} Ein Stylesheet um Mitternacht :root {
--color-palette-background-light: #222;
--color-palette-text-dark: #bbb;
} Advanced: Color Manipulation FunctionsHerleitung: Output is a calculated Calculation is done at compilation (i.e.: expression does not update at runtime) Lösungs-Idee: "context-aware styles"
Nachteile:
Eine Neue Lösungs-Idee: CSS Color Manipulation mit dem HSL/A FarbraumExkurs: Der "Farbkreis", menschliche Farb-Wahrnehmung und der HSL-Farbraum Demo an Hand Color Picker Tool @Component({
selector: 'my-farbkreis',
template: `
<div class="eine-farbe">Dies ist eine Farbe</div>
<div class="komplementaer-farbe">Dies ist ihre Komplementär-Farbe</div>
`,
styles: [`
.eine-farbe {
background-color: var(--color-palette-base);
}
.komplementaer-farbe {
background-color: hsl(
var(--color-palette-base-hue) + 180,
var(--color-palette-base-saturation),
var(--color-palette-base-lightness)
);
}
`]
})
export class FarbkreisComponent {} Vorteil(e):
Nachteil(e):
|
DOM OutletUse CDK Portal API to attach content to another physical location in the DOM. Content Projection: a custom lifecycle hookBecome notified about changes in ViewChildren, ContentChildren. Implement a decorator to subscribe Alternative to Content Projection:
|
Design Systems on AngularMetapher: Automobil Building Blocks for a car: mirror, wheel, exhaust, ... The product: Golf VI, Golf VII, Golf VII Variant, Passat, Polo, ... Can we re-use the same mirror in different car models? Maybe, yes. Contexthttps://unsplash.com/photos/A53o1drQS2k Context: the car model Variationshttps://unsplash.com/photos/JFQE8Ed2pIg Variations: the exhaust needs to work on different car models, thus we need variations of an exhaust Would you build a dedicated exhaust for each car model? Definetely not. We'd build an exhaust that fits into and adapts to the context. Componenthttps://unsplash.com/photos/qWwpHwip31M Actually, the component is not the exhaust, but the moulding blank ("Rohling") of an exhaust. Or: the component is a set of building instructions for the exhaust. Other familiar case studies: web shop and shopping cart:
Cohesion, CouplingThe questions: one shopping cart to rule them all?
Ideal goal: high cohesion, low coupling Button and a Button Toolbar: the button needs to work as part of a toolbar (high cohesion between toolbar and button). The button also needs to work on its own (loose coupling between toolbar and button). A Button...<button myButton>Go ahead</button> <button myButton="primary">Go ahead</button> ...and a Button Toolbar<button-toolbar>
<button myButton>List</button>
<button myButton>Grid</button>
</button-toolbar> What if...
<toolbar>
<toolbar-button>List</toolbar-button>
<toolbar-button>Grid</toolbar-button>
</toolbar> <button myButton="primary-bordered">Go ahead</button>
<button myButton="primary" [myBordered]="true">Go ahead</button>
<button myButton class="btn-primary btn-bordered">Go ahead</button> A Dumb Idea?<primary-btn>Go ahead</primary-btn>
<secondary-btn>Go back</secondary-btn> => lots of possibilties. there may be a golden gun, but there's no silver bullet Open/Closed PrincipleVon... @Directive({
selector: 'button'
})
export class ButtonDirective {} ...zu @Directive({
selector: 'button[myButton]'
})
export class ButtonDirective {
@Input() myButton: 'primary' | 'secondary' = 'primary';
} ...zu @Directive({
selector: '[myButton]'
})
export class ButtonDirective {
@Input() myButton: 'primary' | 'secondary' = 'primary';
} Component vs. Directive
Take Aways: Komponente
Direktive
Mentales Model: Komponente = Direktive + Template Content ProjectionMit Selektoren import { ButtonDirective } from '@my/components/button';
@Component({
selector: 'my-button-toolbar',
template: `<ng-content select="button[myButton]"></ng-content>`
})
export class ButtonToolbarComponent {
@ContentChildren(ButtonDirective)
public buttons$: QueryList<ButtonDirective>;
} Dynamischer Content<my-button-toolbar>
<button myButton *ngFor="let button of allButtons">{{ button.label }}</button>
</my-button-toolbar>
<hr>
<button myButton class="meta-button" (click)="onAddAnotherButton()">Button in Toolbar hinzufügen</button> Lösungs-Idee: @Component({
selector: 'my-button-toolbar',
template: `<ng-content select="button[myButton]"></ng-content>`
})
export class ButtonToolbarComponent implements AfterContentInit, OnDestroy {
@ContentChildren(ButtonDirective)
public buttons$: QueryList<ButtonDirective>;
private buttonContent: Subscription;
ngAfterContentInit() {
this.buttonContent = this.button$.changes.subscribe(
childButtons => { /* react to dynamic content change */ }
);
}
ngOnDestroy() {
if (thus.buttonContent) {
this.buttonContent.unscubscribe();
}
}
} ngOnContentChanges()Syntax Sugar for a @Component({
selector: 'my-button-toolbar',
template: `<ng-content select="button[myButton]"></ng-content>`
})
export class ButtonToolbarComponent implements ContentChanges{
@ContentChildren(ButtonDirective)
public buttons$: QueryList<ButtonDirective>;
ngOnContentChanges(change) {
const buttonChange = change['button$'];
/* react to dynamic content change... */
}
} |
Living collection in comments below
The text was updated successfully, but these errors were encountered: