From c80688b75c723c6810053b50202a657c98e62798 Mon Sep 17 00:00:00 2001
From: Pamela Selle <pamela_selle@cable.comcast.com>
Date: Thu, 13 Aug 2015 15:37:10 -0400
Subject: [PATCH] feat(operator): add defaultIfEmpty

---
 spec/operators/defaultIfEmpty-spec.js | 50 +++++++++++++++++++++++++++
 src/Observable.ts                     |  3 +-
 src/Rx.ts                             |  2 ++
 src/operators/defaultIfEmpty.ts       | 44 +++++++++++++++++++++++
 4 files changed, 98 insertions(+), 1 deletion(-)
 create mode 100644 spec/operators/defaultIfEmpty-spec.js
 create mode 100644 src/operators/defaultIfEmpty.ts

diff --git a/spec/operators/defaultIfEmpty-spec.js b/spec/operators/defaultIfEmpty-spec.js
new file mode 100644
index 0000000000..111d5ac9fd
--- /dev/null
+++ b/spec/operators/defaultIfEmpty-spec.js
@@ -0,0 +1,50 @@
+/* globals describe, it, expect */
+var Rx = require('../../dist/cjs/Rx');
+var Observable = Rx.Observable;
+
+describe('Observable.prototype.defaultIfEmpty()', function () {
+  it('should return the argument if Observable is empty', function (done) {
+    var emptyObservable = Observable.empty();
+    emptyObservable.defaultIfEmpty(2)
+      .subscribe(function(x) {
+        expect(x).toBe(2);
+      }, null, done);
+  });
+
+  it('should return null if the Observable is empty and no arguments', function(done) {
+    var emptyObservable = Observable.empty();
+    emptyObservable.defaultIfEmpty()
+      .subscribe(function(x) {
+        expect(x).toBe(null);
+      }, null, done);
+  });
+
+  it('should return the Observable if not empty with a default value', function(done) {
+    var expected = [1,2,3];
+    var observable = Observable.of(1,2,3);
+    observable.defaultIfEmpty(2)
+      .subscribe(function(x) {
+        expect(x).toBe(expected.shift());
+      }, null, done);
+  });
+
+  it('should return the Observable if not empty with no default value', function(done) {
+    var expected = [1,2,3];
+    var observable = Observable.of(1,2,3);
+    observable.defaultIfEmpty()
+      .subscribe(function(x) {
+        expect(x).toBe(expected.shift());
+      }, null, done);
+  });
+
+  it('should error if the Observable errors', function(done) {
+    var observable = Observable.throw("candy");
+    observable.defaultIfEmpty(2)
+      .subscribe(function(x) {
+        throw "this should not be called";
+      }, function(err) {
+        expect(err).toBe("candy");
+        done();
+      });
+  });
+});
diff --git a/src/Observable.ts b/src/Observable.ts
index 2c9813a1bf..38d256de52 100644
--- a/src/Observable.ts
+++ b/src/Observable.ts
@@ -130,7 +130,8 @@ export default class Observable<T> {
   takeUntil: (observable: Observable<any>) => Observable<T>;
   partition: (predicate: (x: T) => boolean) => Observable<T>[];
   toPromise: (PromiseCtor: PromiseConstructor) => Promise<T>;
-  
+  defaultIfEmpty: <T, R>(defaultValue: R) => Observable<T>|Observable<R>;
+
   observeOn: (scheduler: Scheduler, delay?: number) => Observable<T>;
   subscribeOn: (scheduler: Scheduler, delay?: number) => Observable<T>;
 
diff --git a/src/Rx.ts b/src/Rx.ts
index 3d82fdaed2..6b12698a40 100644
--- a/src/Rx.ts
+++ b/src/Rx.ts
@@ -123,9 +123,11 @@ observableProto.subscribeOn = subscribeOn;
 
 import partition from './operators/partition';
 import toPromise from './operators/toPromise';
+import defaultIfEmpty from './operators/defaultIfEmpty';
 
 observableProto.partition = partition;
 observableProto.toPromise = toPromise;
+observableProto.defaultIfEmpty = defaultIfEmpty;
 
 import _catch from './operators/catch';
 import retryWhen from './operators/retryWhen';
diff --git a/src/operators/defaultIfEmpty.ts b/src/operators/defaultIfEmpty.ts
new file mode 100644
index 0000000000..46c03c4408
--- /dev/null
+++ b/src/operators/defaultIfEmpty.ts
@@ -0,0 +1,44 @@
+import Operator from '../Operator';
+import Observer from '../Observer';
+import Observable from '../Observable';
+import Subscriber from '../Subscriber';
+
+import tryCatch from '../util/tryCatch';
+import {errorObject} from '../util/errorObject';
+import bindCallback from '../util/bindCallback';
+
+export default function defaultIfEmpty<T,R>(defaultValue: R = null) : Observable<T>|Observable<R> {
+  return this.lift(new DefaultIfEmptyOperator(defaultValue));
+}
+
+export class DefaultIfEmptyOperator<T, R> extends Operator<T, R> {
+
+  constructor(private defaultValue: R) {
+    super();
+  }
+
+  call(observer: Observer<T>): Observer<T> {
+    return new DefaultIfEmptySubscriber(observer, this.defaultValue);
+  }
+}
+
+export class DefaultIfEmptySubscriber<T, R> extends Subscriber<T> {
+
+  isEmpty: boolean = true;
+
+  constructor(destination: Observer<T>, private defaultValue: R) {
+    super(destination);
+  }
+
+  _next(x) {
+    this.isEmpty = false;
+    this.destination.next(x);
+  }
+
+  _complete() {
+    if(this.isEmpty) {
+      this.destination.next(this.defaultValue);
+    }
+    this.destination.complete();
+  }
+}