Skip to content

Instantly share code, notes, and snippets.

@alvipeo
Last active March 31, 2016 00:08
Show Gist options
  • Save alvipeo/36de30bdccd126063344 to your computer and use it in GitHub Desktop.
Save alvipeo/36de30bdccd126063344 to your computer and use it in GitHub Desktop.
Change detection OnPush
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
//map tells the System loader where to look for things
map: {
app: './src'
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
upload: './upload.ts',
typingText: './typingText.ts',
defaultExtension: 'ts'
}
}
});
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>playground</title>
<link rel="stylesheet" href="styles.css">
<script src="https://code.angularjs.org/2.0.0-beta.12/angular2-polyfills.js"></script>
<script src="https://code.angularjs.org/tools/system.js"></script>
<script src="https://code.angularjs.org/tools/typescript.js"></script>
<script src="config.js"></script>
<script src="https://npmcdn.com/@reactivex/rxjs@5.0.0-beta.4/dist/global/Rx.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.12/angular2.dev.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.12/http.dev.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.12/router.dev.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
</head>
<body>
<app></app>
</body>
</html>
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {Observable} from 'rxjs/Rx';
import {IlgUploader} from "./upload";
@Component({
selector: 'app',
template: `<div>
<ol><li>Click on select files</li><li>Select file (upload not implemented)</li><li>Click start</li><li>You'll see an Error message below</li></ol>
<hr/>
<ilg-uploader></ilg-uploader>
<hr/>
<p>When a user starts uploading files, every one of them will have a progress (uploading state). This should be reflected on a view. But since OnPush, is it the only possible method - to use ChangeDetectorRef and its markForCheck() method?</p>
<p>I know OnPush means CD kicks in only when @Input() props change. But what if changing @Input() prop starts a whole new internal (to component) process with its state changes which, in turn, should be reflected on a view?</p>
<p>What's interesting is why TypingText works without using ChangeDetectorRef and Upload doesn't? Just because TypingText.showError$ is an Observable and Upload.errorText is not?</p>
<p>What's the best of doing this really?</p>
</div>`,
directives: [IlgUploader]
})
export class App {
constructor() {}
}
bootstrap(App, []);
import {Input, Component, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit, OnChanges, SimpleChange} from "angular2/core";
import {Observable} from 'rxjs/Rx';
@Component({
selector: "ilg-flying-text",
template: "{{ showError$ | async }}",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IlgFlyingText implements AfterViewInit, OnChanges {
@Input() text: string;
showError$: Observable<string>;
ngAfterViewInit() {
this.initObservable();
}
ngOnChanges(changes: { [propName: string]: SimpleChange }) {
this.initObservable();
}
private initObservable() {
if (!!this.text) {
console.log(this.text);
this.showError$ = this.createErrorObservable(this.text);
} else {
this.showError$ = Observable.of(null);
}
}
private createErrorObservable(errText: string): Observable<string> {
//console.log(err);
let o1$ = Observable.from(errText);
let o2$ = Observable.interval(25);
//let o3$ = o1$.flatMap(v => v);
//console.log(o3$);
let o$ = o1$.zip(o2$)
.map(tuple => tuple[0])
.scan((acc, val) => acc += val[0]);
//.do(val => console.log(val));
return o$;
}
}
import {Input, Component, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit, OnChanges, SimpleChange, ViewChild} from "angular2/core";
import { CORE_DIRECTIVES, FORM_DIRECTIVES, FormBuilder, ControlGroup, Validators, Control } from 'angular2/common';
import {IlgFlyingText} from './typingText';
@Component({
selector: "ilg-uploader",
template: `<div>
<p>Drag files to any place here or ...</p>
<a href="#" (click)="onSelectFilesBtnClick($event)">Select files to Upload</a>
<form [ngFormModel]="frmUpload">
<input #fileInput type="file" (change)="onFilesChanged($event)" multiple accept="image/*" [ngFormControl]="filesInput" hidden />
<div *ngIf="selectedFiles">
<ul>
<li *ngFor="#file of selectedFiles">{{file.name}}</li>
</ul>
<button type="button" (click)="onStartUpload()">Start</button>
</div>
</form>
</div>
<div class="alert alert-danger" *ngIf="errorText">
<strong>Error!</strong> <ilg-flying-text [text]="errorText"></ilg-flying-text>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
directives: [CORE_DIRECTIVES, IlgFlyingText],
})
export class IlgUploader {
@ViewChild("fileInput") fileInput: any;
private selectedFiles: [];
private errorText: string;
constructor(private cd: ChangeDetectorRef, private fb: FormBuilder) {
this.frmUpload = fb.group({
"filesInput": ["", Validators.required]
});
this.filesInput = this.frmUpload.controls["filesInput"] as Control;
}
onSelectFilesBtnClick($event: any) {
$event.preventDefault();
this.fileInput.nativeElement.click();
}
onFilesChanged($event: any) {
this.clearError();
this.selectedFiles = [];
for (let i = 0; i < $event.target.files.length; i++) {
const file: File = $event.target.files[i];
this.selectedFiles.push({name: file.name});
}
}
onStartUpload() {
this.clearError();
// suppose we get an error here
this.errorText = "Something bad happened!";
}
private clearError() {
this.errorText = null;
this.cd.markForCheck();
}
}
/* todo: add styles */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment