<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Real World Full Stack - Medium]]></title>
        <description><![CDATA[Full Stack development of real world applications - Medium]]></description>
        <link>https://blog.realworldfullstack.io?source=rss----5fcb8756dcc3---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Real World Full Stack - Medium</title>
            <link>https://blog.realworldfullstack.io?source=rss----5fcb8756dcc3---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 15 Apr 2026 23:18:33 GMT</lastBuildDate>
        <atom:link href="https://blog.realworldfullstack.io/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Real world App: Part 25[a]- NativeScript, Angular and Firebase]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-25-a-nativescript-and-angular-e9ff4b102e9b?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/e9ff4b102e9b</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[nativescript]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[firebase]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sun, 27 Jan 2019 01:10:13 GMT</pubDate>
            <atom:updated>2019-01-27T17:00:46.096Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Going native with NativeScript</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NaYn11yOE6j01fXb" /><figcaption>Photo by <a href="https://unsplash.com/@franckinjapan?utm_source=medium&amp;utm_medium=referral">Franck V.</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><em>This is Part 25 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Why I chose NativeScript?</h4><p>After looking at several alternates (see <a href="https://www.konstantinfo.com/blog/react-native-vs-flutter-vs-ionic-vs-nativescript-vs-pwa/">this</a> and several other comparisons on the web), in order to leverage maximum code reuse and not rewrite our app completely for mobile platforms, it came down to 2 choices -</p><ul><li>Hybrid PWA app with <a href="https://ionicframework.com/">Ionic</a></li><li>Native app with <a href="https://www.nativescript.org/">NativeScript</a></li></ul><p>The easiest choice would have been to go with the hybrid approach and use Ionic as it would have required less effort and leveraged the existing tech stack to accomplish this. For me however, performance was a big consideration, especially for a growing game app like ours, so I decided to go with NativeScript.</p><p>The vast majority of code changes would involve rewriting the components, along with it’s templates and css, since NativeScript uses a markup language in XML format that is different from HTML to compose the UI for the native platforms.</p><p>In addition, since the <strong>firebase plugin for NativeScript</strong> has a different API surface, we’ll create an interface for interacting with Firebase with different implementations for web and mobile. We can then inject the appropriate implementation based on the platform.</p><h4>Installing NativeScript on Mac OS</h4><p>In order to simulate, debug and deploy to iOS platform, I switched to Mac OS for development.</p><p>Installing NativeScript on Mac -</p><ul><li><a href="https://docs.nativescript.org/angular/start/quick-setup">https://docs.nativescript.org/angular/start/quick-setup</a></li><li><a href="https://docs.nativescript.org/angular/start/ns-setup-os-x">https://docs.nativescript.org/angular/start/ns-setup-os-x</a></li></ul><ol><li>Install Xcode from App Store</li><li>Use <a href="https://github.com/creationix/nvm">nvm</a> to install Nodejs. nvm allows you to install and switch between multiple versions of node. nvm also takes away the need to use sudo for installing npm packages.</li></ol><pre>curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash</pre><p>Close terminal window and install latest version of node 8</p><pre>nvm --version<br>nvm install 8.14.0</pre><p>3. Install <a href="https://brew.sh/">Homebrew</a> (homebrew helps in installation of other packages)</p><pre>ruby -e &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&quot;</pre><p>4. Install other dependencies for iOS</p><p>a. Install Xcode command line tools, if not already installed.</p><p>Verify Xcode command line tools -</p><pre>xcodebuild -version</pre><p>If not installed, start Xcode and you will be prompted to install it.</p><p>b. Install the <a href="https://rubygems.org/gems/xcodeproj/versions/0.28.2">xcodeproj ruby gem</a> with the following command</p><pre>sudo gem install xcodeproj</pre><p>c. Install &amp; setup <a href="https://guides.cocoapods.org/using/getting-started.html">CocoaPods</a></p><pre>sudo gem install cocoapods<br>pod setup</pre><p>d. Install python six package (if not already installed)</p><p>(got this: Requirement already satisfied: six in /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python (1.4.1))</p><pre>sudo easy_install pip<br>pip install six</pre><p>5. Install Android dependencies</p><p>a. Install <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 8</a>.</p><pre>brew tap caskroom/versions<br>brew cask install java8</pre><p>b. Set JAVA_HOME</p><pre>export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)</pre><p>c. Install Android Studio from <a href="https://developer.android.com/studio/install">https://developer.android.com/studio/install</a></p><p>d. Add this to ~/.bash_profile</p><pre>export ANDROID_HOME=&quot;$HOME/Library/Android/sdk&quot;</pre><p>6. Install NativeScript cli</p><pre>npm i -g nativescript</pre><p>Restart terminal</p><p>7. Verify all installation was ok.</p><pre>tns doctor</pre><p>At this point, NativeScript and all iOS and Android dependencies are installed and we’re ready to get going with our project</p><h4>Add NativeScript to our application</h4><p>Let’s start by adding NativeScript support to our app -</p><pre>ng add <a href="http://twitter.com/nativescript/schematics">@nativescript/schematics</a></pre><p>This will the necessary npm modules, tsconfig and the module files. We would add the remaining dependencies to our <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/package.json">package.json</a> manually.</p><h4>Building the apps</h4><p>To build our mobile apps, we will use the nativescript-dev-webpack plugin. The build process handles replacing the web files with the ones with “.tns” extension when available.</p><p>For example, “core.module.tns.ts” will replace “core.module.ts” when building for mobile; while the tns files will be ignored for web builds.</p><p>Here is the complete <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/webpack.config.js">webpack.config.js</a> for nativescript build.</p><p>The <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/trivia/tsconfig.app.json">tsconfig.app.json</a> is also modified to ignore mobile specific files.</p><p>To build the mobile apps we use the following commands -</p><pre>tns run ios --bundle<br>tns run android --bundle</pre><h4>Mobile UI</h4><p>The UI for our mobile app will be slightly different from our web application ui with some different interactions to leverage mobile platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3S7bBlHY9L589TQoGI2YNg.jpeg" /><figcaption>Mobile App UI design</figcaption></figure><h4>NativeScript Components</h4><p>Similar to an Angular component, a typical NativeScript-Angular component consists of a component class, a template and a css(scss) file. Ideally, the component logic remains the same and the same component class can be used for both web &amp; mobile platforms.</p><p>More often than not, there are subtle differences between the implementation. In those cases, we can create a base class for the common stuff and have 2 separate component classes — one for web and the other for mobile to implement the differences.</p><p>Let’s look at a couple of the examples -</p><p>The <a href="https://github.com/anihalaney/rwa-trivia/tree/part-25/projects/trivia/src/app/components/question">question.component</a> has similar logic for both the web and mobile versions. So we have use the same class in <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/trivia/src/app/components/question/question.component.ts">question.component.ts</a> file shared between them while creating a tns file for the mobile version -</p><pre>&lt;!-- <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/trivia/src/app/components/question/question.component.tns.html">question.component.tns.html</a> --&gt;</pre><pre>&lt;CardView class=&quot;cardStyle&quot; margin=&quot;10&quot; android:elevation=&quot;20&quot; ios:elevation=&quot;40&quot; radius=&quot;5&quot; class=&quot;gameplay&quot; backgroundColor=&quot;white&quot;&gt;<br>    &lt;StackLayout class=&quot;cardStyle Roboto-Medium&quot; backgroundColor=&quot;#ffffff&quot; height=&quot;100%&quot; width=&quot;100%&quot; margin=&quot;15&quot;&gt;<br>        &lt;Label text=&quot;Question of the day!&quot; color=&quot;#000&quot; fontSize=&quot;20&quot; class=&quot;text-center&quot; textWrap=&quot;true&quot;&gt;&lt;/Label&gt;<br><br>        &lt;Label [text]=&quot;question?.questionText&quot; color=&quot;#263238&quot; fontSize=&quot;18&quot; fontWeight=&quot;500&quot; margin=&quot;10&quot; textWrap=&quot;true&quot;&gt;&lt;/Label&gt;<br><br>        &lt;StackLayout orientation=&quot;horizontal&quot; margin=&quot;10&quot; *ngIf=&quot;question&quot;&gt;<br>            &lt;Label text=&quot;Tags:&quot; marginRight=&quot;5&quot; color=&quot;#1B1C1C&quot; fontSize=&quot;15&quot; fontWeight=&quot;500&quot; textWrap=&quot;true&quot;&gt;&lt;/Label&gt;<br>            &lt;Label text=&quot;{{question.tags?.toString()}}&quot; class=&quot;Roboto-Regular&quot; color=&quot;#1B1C1C&quot; fontSize=&quot;15&quot; textWrap=&quot;true&quot;&gt;&lt;/Label&gt;<br>        &lt;/StackLayout&gt;<br>        &lt;StackLayout *ngIf=&quot;question&quot;&gt;<br>            &lt;StackLayout class=&quot;question-answered Hind-Medium&quot; margin=&quot;10&quot; *ngFor=&quot;let answer of question.answers&quot;<br>            [class.players-answer]=&quot;answeredText === answer.answerText&quot; [class.wrong]=&quot;answeredText === answer.answerText &amp;&amp; correctAnswerText !== answer.answerText&quot;<br>            [class.right]=&quot;answeredText !== &#39;&#39; &amp;&amp; correctAnswerText === answer.answerText&quot; (tap)=&quot;answerButtonClicked(answer)&quot; [isUserInteractionEnabled] = &quot;doPlay&quot;&gt;<br>            &lt;Label text=&quot;{{answer?.answerText}}&quot; horizontalAlignment=&quot;center&quot; fontSize=&quot;17&quot; fontWeight=&quot;500&quot; marginTop=&quot;15&quot;<br>                marginBottom=&quot;15&quot; textWrap=&quot;true&quot;&gt;&lt;/Label&gt;<br>        &lt;/StackLayout&gt;<br>        &lt;/StackLayout&gt;<br>        &lt;StackLayout orientation=&quot;vertical&quot; *ngIf=&quot;question&quot; margin=&quot;10&quot;&gt;<br>            &lt;author [userDict]=&quot;userDict&quot; [userId]=&quot;question.created_uid&quot;&gt;&lt;/author&gt;<br>            &lt;button class=&quot;Roboto-Regular&quot; backgroundColor=&quot;#8ac541&quot; color=&quot;white&quot; margin=&quot;20&quot; (tap)=&quot;getNextQuestion()&quot; *ngIf=&quot;answeredText!==&#39;&#39;&quot;<br>                width=&quot;100%&quot; text=&quot;Try Another&quot; fontSize=&quot;17&quot; padding=&quot;15&quot;&gt;&lt;/button&gt;<br>        &lt;/StackLayout&gt;<br>    &lt;/StackLayout&gt;<br><br>&lt;/CardView&gt;</pre><p>On the other hand, the <a href="https://github.com/anihalaney/rwa-trivia/tree/part-25/projects/trivia/src/app/components/dashboard">dashboard component</a> has slightly different interactions in the mobile and web versions. So we not only have a different tns.html file, but also a different component class that is derived from a common base class.</p><p>You can find the base class, the web version and the mobile version <a href="https://github.com/anihalaney/rwa-trivia/tree/part-25/projects/trivia/src/app/components/dashboard">here</a>.</p><h4>NativeScript - Firebase</h4><p>The <a href="https://www.npmjs.com/package/nativescript-plugin-firebase">nativescript-plugin-firebase</a> allows integration with firebase. However, the plugin does not provide the same interface as the web version of firebase package. Also, the plugin returns Promises instead of the Observables that the firebase library for web does.</p><p>We define a common interface that can be used for all platforms -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/db-service/db.service.ts">db.service.ts</a></pre><pre>import { Injectable, Inject, NgZone } from &#39;@angular/core&#39;;<br>import { Observable, of } from &#39;rxjs&#39;;<br><br>@Injectable()<br>export abstract class DbService {<br><br>    constructor() {<br><br>    }<br><br>    public createDoc(collectionName: string, document: any): any {<br>    }<br><br>    public setDoc(collectionName: string, docId: any, document: any): any {<br><br>    }<br><br>    public updateDoc(collectionName: string, docId: any, document: any) {<br><br>    }<br>    public valueChanges(collectionName: string, path?: any, queryParams?: any): Observable&lt;any&gt; {<br>        return of();<br>    }<br><br>    public createId(): any {<br><br>    }<br>    public getFireStorageReference(filePath: string): any {<br><br>    }<br><br>    public getFireStore(): any {<br><br>    }<br><br>    public getDoc(collectionName: string, docId: any): any {<br><br>    }<br><br>    public upload(filePath: string, imageBlob: any): any {<br><br>    }<br>}</pre><p>And then the implementation for <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/db-service/web/db.service.ts">web</a> and <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/db-service/mobile/db.service.ts">mobile</a>.</p><h4>ngZone</h4><p>The nativescript-firebase plugin runs outside Angular. In order for Angular to run it’s change detection, we need to call ngZone.run when listening to a firebase event. We use this in implementation of the mobile version for DbService.</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/db-service/mobile/db.service.ts">db.service.ts</a></pre><pre>@Injectable()<br>export class TNSDbService extends DbService {<br><br>    constructor(private zone: NgZone,<br>        protected _store: Store&lt;any&gt;,<br>        private userActions: UserActions) {<br>        super();<br>    }<br>...</pre><pre>    public valueChanges(collectionName: string, path?: any, queryParams?: any): Observable&lt;any&gt; {<br><br>        let query = firebaseApp.firestore().collection(collectionName);<br><br>        if (queryParams) {<br>            for (const param of queryParams.condition) {<br>                query = query.where(param.name, param.comparator, param.value);<br>            }<br>            if (queryParams.orderBy) {<br>                for (const param of queryParams.orderBy) {<br>                    query = query.orderBy(param.name, param.value);<br>                }<br>            }<br>            if (queryParams.limit) {<br>                query = query.limit(queryParams.limit);<br>            }<br>        }<br>        if (path) {<br>            query = query.doc(path);<br>        }<br>        return Observable.create(observer =&gt; {<br>            const unsubscribe = query.onSnapshot((snapshot: any) =&gt; {<br>                let results = [];<br>                if (snapshot &amp;&amp; snapshot.forEach) {<br>                    snapshot.forEach(doc =&gt; results.push({<br>                        id: doc.id,<br>                        ...doc.data()<br>                    }));<br>                } else {<br>                    results = snapshot.data();<br>                }<br>                this.zone.run(() =&gt; {<br>                    if (results !== undefined) {<br>                        observer.next(results);<br>                    } else {<br>                        observer.next(undefined);<br>                    }<br>                });<br>            });<br>            return () =&gt; unsubscribe();<br>        });<br>    }<br>...<br>}</pre><p>And we do the same for FirebaseAuthService -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/auth/firebase-auth.service.ts">firebase-auth.service.ts</a></pre><pre>import { Observable } from &#39;rxjs&#39;;<br><br>export abstract class FirebaseAuthService {<br>    abstract createUserWithEmailAndPassword(email, password);<br>    abstract authState(): Observable&lt;any&gt;;<br>    abstract signOut();<br>    abstract showLogin();<br>    abstract getIdToken(user, forceRefresh: boolean);<br>    abstract refreshToken(forceRefresh: boolean): Promise&lt;string&gt;;<br>    abstract signInWithEmailAndPassword(email: string, password: string);<br>    abstract sendEmailVerification(user): Promise&lt;any&gt;;<br>    abstract sendPasswordResetEmail(email: string): Promise&lt;any&gt;;<br>    abstract firebaseAuth(): any;<br>    abstract googleLogin(): Promise&lt;any&gt;;<br>    abstract facebookLogin(): Promise&lt;any&gt;;<br>    abstract twitterLogin(): Promise&lt;any&gt;;<br>    abstract githubLogin(): Promise&lt;any&gt;;<br>}</pre><p>And it’s implementation here - <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/auth/web/firebase-auth.service.ts">web</a> &amp; <a href="https://github.com/anihalaney/rwa-trivia/blob/part-25/projects/shared-library/src/lib/core/auth/mobile/firebase-auth.service.ts">mobile</a>.</p><h4>Modules and Routing</h4><p>We then go on to create a mobile version of the modules and routes to include only the necessary modules for the mobile app. The complete code for this part is <a href="https://github.com/anihalaney/rwa-trivia/tree/part-25">here</a>.</p><h4>Testing the app on the simulator</h4><p>To test the app in the simulator -</p><pre>tns run android--bundle</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*baUrbyQL4Go2SI_WspX_Fg.png" /><figcaption>Testing on Android simulator</figcaption></figure><p>Similarly, for iOS</p><pre>tns run ios --bundle</pre><h4>Next up</h4><p>In this part, we started to build out the native version of the app and tested it using the iOS and Android simulator. In a follow up part to this 25[b], we’ll see how we can deploy this to the actual devices.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series: <a href="https://blog.realworldfullstack.io">https://blog.realworldfullstack.io</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing; <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">X: Upgrading to Angular 4</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">19: Two Player</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">20: Upgrading to Angular 6</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-21-service-workers-pwa-with-angular-3ba5c7168f3f">21: PWA</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-22-angular-testing-with-protractor-jasmine-and-jest-6a0e03a89038">22: e2e Testing</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-23-ssr-with-angular-universal-637ec8490c44">23: SSR with Angular Universal</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-24-angular-workspace-69345d32e00e">24: Angular Workspace</a></li></ul><h4>References</h4><ul><li><a href="https://www.konstantinfo.com/blog/react-native-vs-flutter-vs-ionic-vs-nativescript-vs-pwa/">https://www.konstantinfo.com/blog/react-native-vs-flutter-vs-ionic-vs-nativescript-vs-pwa/</a></li><li><a href="https://brew.sh/">https://brew.sh/</a></li><li><a href="https://github.com/creationix/nvm">https://github.com/creationix/nvm</a></li><li><a href="https://nativescript.org">https://nativescript.org</a></li><li><a href="https://docs.nativescript.org/angular/start/introduction">https://docs.nativescript.org/angular/start/introduction</a></li><li><a href="https://blog.angular.io/apps-that-work-natively-on-the-web-and-mobile-9b26852495e7">https://blog.angular.io/apps-that-work-natively-on-the-web-and-mobile-9b26852495e7</a></li><li><a href="https://github.com/NathanWalker/angular-seed-advanced/wiki/How-to-integrate-Firebase-across-all-platforms-(web---nativescript---desktop)">https://github.com/NathanWalker/angular-seed-advanced/wiki/How-to-integrate-Firebase-across-all-platforms-(web---nativescript---desktop)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e9ff4b102e9b" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-25-a-nativescript-and-angular-e9ff4b102e9b">Real world App: Part 25[a]- NativeScript, Angular and Firebase</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App: Part 24 - Angular Workspace]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-24-angular-workspace-69345d32e00e?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/69345d32e00e</guid>
            <category><![CDATA[angular-cli]]></category>
            <category><![CDATA[angular-workspace]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sat, 17 Nov 2018 23:27:14 GMT</pubDate>
            <atom:updated>2018-11-17T23:28:44.448Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Using Angular workspace to split up the app into project application and libraries</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-nHyqdTQbJScmP5K" /><figcaption>Workspace - Photo by <a href="https://unsplash.com/@jtylernix?utm_source=medium&amp;utm_medium=referral">Tyler Nix</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><em>This is Part 24 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Angular workspace</h4><blockquote>A workspace is a way to split up your application into smaller units called projects. There are 2 types of projects - <strong>library and application</strong>.</blockquote><p>A workspace <em>library</em> is a logical grouping of modules that can be re-used across workspace projects. A library can also be published &amp; distributed to be re-used in other applications.</p><p>A workspace <em>application</em> is a single-page application (SPA) that may or may not consume one or more libraries.</p><p>Angular introduced the concept of workspaces in version 6. Prior to version 6, the concept of workspaces was introduced by the nrwl.io team - <a href="https://nrwl.io/nx">https://nrwl.io/nx</a>.</p><h4>Why workspaces? Why now?</h4><p>The admin module of our application was never meant to be part of the main app. Even though the module was lazy loaded and behind a route guard, the admin users are different from the users of the rest of the site. Also, the functionality of this module is so distinct from the main site, that it’ll almost never be used along the main site. The state that supports this functionality is different than the state that’s on the main site.</p><p>As our main app is reaching minimum viable functionality, we also need to start development of our mobile app. This will only add to the complexity of the existing application. The mobile app will not have any of the admin functionality.</p><p>This part of the series will focus on simply refactoring our existing application into 3 Angular projects - we will create 2 application projects (trivia and trivia-admin) and one library project, for now, that’s shared between the two. Let’s get right into it.</p><h4>Generating the new application</h4><p>First we’ll rename our project in angular.json temporarily, so we can create another project with the same name. Let’s rename our project to triviatmp in angular.json</p><p>Next, let’s generate a new one -</p><pre>ng generate application --name=trivia</pre><p>This would generate a new project under /projects/trivia and create a corresponding section in angular.json. Let’s the /projects/trivia/src with our /src folder, and then replace the /projects/trivia-e2e/src with contents of /e2e.</p><p>Next, we modify angular.json, project trivia to match triviatmp (including styles, assets, serviceworker, dev build configuration), but keeping the folder paths the same. Let’s try it out -</p><pre>ng serve</pre><p>Note: I did have some incorrect import paths that I needed to fix.</p><p>Remove the tsconfigs and typings from inside the projects/trivia/<strong>src</strong> folder as they are now at the project/trivia level.</p><h4>For unit testing</h4><p>I also had to modify the jest setting in package.json (path to setup and tsconfig.spec), had to move the setup-jest out of src and modify the path to mocks.</p><h4>For e2e testing</h4><p>Simply remove protractor.conf.js from root folder and then use command “<em>ng e2e trivia-e2e</em>”</p><p>Trivia project folder structure -</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/270/1*U7jGr6d1lqpn2o5loLr-zA.png" /><figcaption>trivia</figcaption></figure><h4>Admin &amp; Shared library projects</h4><p>Let’s create the other projects. This will also modify our <a href="https://github.com/anihalaney/rwa-trivia/blob/part-24/angular.json">angular.json</a> -</p><pre>ng generate application --name=trivia-admin --prefix=app<br>ng generate library --name=shared-library --prefix=stl</pre><p>We would then carefully replace the contents of each of the src folders of these projects with what we need from our main project.</p><p>The core module along with the services and the shared module with our model will be moved to the shared-library project.</p><p>Admin and bulk (for admin) modules will be moved to the admin project. Our application folder structure for the 3 projects will looks something like this -</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/257/1*Ca1cArK4T1eXw-yKAIlFxg.png" /><figcaption>shared-library</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/263/1*X_34OmZmxwk5Tj5QKwhIfQ.png" /><figcaption>trivia-admin</figcaption></figure><p>To run these applications -</p><pre>ng serve trivia</pre><pre>ng server trivia-admin</pre><p>The complete code for this part is <a href="https://github.com/anihalaney/rwa-trivia/tree/part-24">here</a>. Even though the <a href="https://github.com/anihalaney/rwa-trivia/pull/234/files">PR</a> shows a daunting 852 files changed, most of this is just moving the files around and changing import paths.</p><h4>Next up</h4><p>In this part, we broke up our app into more maintainable workspace projects. In the next part, we will introduce NativeScript and start developing our mobile app.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing; <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">X: Upgrading to Angular 4</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">19: Two Player</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">20: Upgrading to Angular 6</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-21-service-workers-pwa-with-angular-3ba5c7168f3f">21: PWA</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-22-angular-testing-with-protractor-jasmine-and-jest-6a0e03a89038">22: e2e Testing</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-23-ssr-with-angular-universal-637ec8490c44">23: SSR with Angular Universal</a></li></ul><h4>References</h4><ul><li><a href="https://blog.angularindepth.com/creating-a-library-in-angular-6-87799552e7e5">https://blog.angularindepth.com/creating-a-library-in-angular-6-87799552e7e5</a></li><li><a href="https://angular.io/guide/file-structure">https://angular.io/guide/file-structure</a></li><li><a href="https://nitayneeman.com/posts/understanding-the-angular-cli-workspace-file/">https://nitayneeman.com/posts/understanding-the-angular-cli-workspace-file/</a></li><li><a href="https://medium.com/@angularlicious/angular-6-workspace-test-drive-cfe24bbceeb3">https://medium.com/@angularlicious/angular-6-workspace-test-drive-cfe24bbceeb3</a></li><li><a href="https://nrwl.io/nx">https://nrwl.io/nx</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=69345d32e00e" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-24-angular-workspace-69345d32e00e">Real World App: Part 24 - Angular Workspace</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 23: SSR with Angular Universal]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-23-ssr-with-angular-universal-637ec8490c44?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/637ec8490c44</guid>
            <category><![CDATA[server-side-rendering]]></category>
            <category><![CDATA[seo]]></category>
            <category><![CDATA[angular-universal]]></category>
            <category><![CDATA[firebase-cloud-functions]]></category>
            <category><![CDATA[firebase]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sun, 11 Nov 2018 21:30:12 GMT</pubDate>
            <atom:updated>2018-11-13T00:52:03.728Z</atom:updated>
            <content:encoded><![CDATA[<p>Server side rendering with Angular universal and firebase</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FaBgpdnBcOGfdXEm" /><figcaption>“artist rendering” by <a href="https://unsplash.com/@jonfordphotos?utm_source=medium&amp;utm_medium=referral">jonathan Ford</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><em>This is Part 23 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Server side rendering with Angular</h4><p>(see <a href="https://angular.io/guide/universal">https://angular.io/guide/universal</a>)</p><p>A normal Angular application executes in the <em>browser</em>, rendering pages in the DOM in response to user actions and events. Server side rendering (SSR) is a process of generating static application pages on the server.</p><p>Angular Universal can generate and serve those SSR pages in response to requests from browsers. It can also pre-generate pages as HTML files that you serve later.</p><h4>Why use SSR?</h4><p>The three main reasons to create a SSR version of your app.</p><ul><li>Facilitate web crawlers (SEO)</li><li>Improve performance on mobile and low-powered devices</li><li>Show the first page quickly</li></ul><h4>Search Engine Optimization (SEO)</h4><p>Search engines like Google &amp; Bing &amp; social media sites like Facebook &amp; Twitter rely on web crawlers to index your application content and make that content searchable on the web. These web crawlers may be unable to navigate and index your highly interactive Angular application as a human user could do, even though google’s engine does a pretty decent job of executing javascript.</p><p>An SSR application can generate a static version of your app that is easily searchable, linkable, and navigable without JavaScript. It also makes a site preview available since each URL returns a fully rendered page.</p><p>As it’s important for our app to be discoverable by search engines, this was the primary reason, I wanted to have SSR early in our development cycle. The primary page that needs to be server-side rendered is the dashboard.</p><p>Angular universal allows us to render on the server (without a need for web browser) with minimal change to our existing code.</p><h4>Universal with Express/NodeJS</h4><p>Angular universal provides a template engine for Express on NodeJS which works for us as our firebase functions run on node engine. Let’s get right into it.</p><p>We’ll start by adding dependencies to <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/package.json">package.json</a> (and also update existing ones) -</p><pre>&quot;dependencies&quot;: {<br>...<br> &quot;@angular/platform-server&quot;: &quot;^6.0.7&quot;,<br> &quot;@nguniversal/express-engine&quot;: &quot;^6.0.0&quot;,<br> &quot;@nguniversal/module-map-ngfactory-loader&quot;: &quot;^6.0.0&quot;,<br> &quot;domino&quot;: &quot;^2.0.2&quot;,<br> &quot;ts-loader&quot;: &quot;^4.2.0&quot;,<br> &quot;webpack-cli&quot;: &quot;^3.0.8&quot;,<br> &quot;xmlhttprequest&quot;: &quot;^1.8.0&quot;<br>...<br>}</pre><p>Let’s see what each of them does -</p><ul><li><em>platform-server</em>: server implementation of DOM</li><li><em>express-engine</em>: Express Engine for running Angular Apps on server for SSR</li><li><em>module-map-ngfactory-loader</em>: lazy-loading on server</li><li><em>domino (</em><strong><em>DOM in no</em></strong><em>de)</em>: additional support for server side DOM manipulation</li><li><em>xmlhttprequest</em>: wrapper for the built-in http client to emulate the browser XMLHttpRequest object</li><li>webpack-cli: for configuration and compilation of the server package</li><li>ts-loader: typescript loader for webpack</li></ul><p>Let’s add a server.ts that’ll run in our express engine to render the application on the server. Note the use of xmlhttprequest that’ll be used for http requests on the server to emulate a browser’s XmlHttpRequest. Also we use domino for any references to window for SSR.</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/server.ts">server.ts</a><br>import &#39;zone.js/dist/zone-node&#39;;<br>import &#39;reflect-metadata&#39;;<br><br>import { enableProdMode } from &#39;@angular/core&#39;;<br><br>import * as express from &#39;express&#39;;<br>import { ngExpressEngine } from &#39;@nguniversal/express-engine&#39;;<br>import { provideModuleMap } from &#39;@nguniversal/module-map-ngfactory-loader&#39;;<br>import { resolve } from &#39;path&#39;;<br><br>const domino = require(&#39;domino&#39;);<br>const win = domino.createWindow(&#39;&#39;);<br><br>global[&#39;window&#39;] = win;<br>global[&#39;document&#39;] = win.document;<br>global[&#39;XMLHttpRequest&#39;] = require(&#39;xmlhttprequest&#39;).XMLHttpRequest;<br><br>console.log(process.cwd());<br>const DIST_FOLDER = resolve(process.cwd(), &#39;./dist&#39;);<br>console.log(DIST_FOLDER);<br><br>const {<br>  AppServerModuleNgFactory,<br>  LAZY_MODULE_MAP<br>} = require(`./functions/dist/server/main`);<br><br>enableProdMode();<br><br>const app = express();<br><br>// Set the engine<br>app.engine(<br>  &#39;html&#39;,<br>  ngExpressEngine({<br>    bootstrap: AppServerModuleNgFactory,<br>    providers: [provideModuleMap(LAZY_MODULE_MAP)]<br>  })<br>);<br><br>app.set(&#39;view engine&#39;, &#39;html&#39;);<br><br>app.set(&#39;views&#39;, DIST_FOLDER);<br><br>// Point all routes to Universal<br>app.get(&#39;*&#39;, (req, res) =&gt; {<br>  res.render(&#39;index&#39;, { req });<br>});<br><br><br>exports.app = app;</pre><p>Next we add a root module for the server -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/src/app/app.server.module.ts">app.server.module.ts</a></pre><pre>import { NgModule } from &#39;@angular/core&#39;;<br>import { ServerModule, ServerTransferStateModule } from &#39;@angular/platform-server&#39;;<br>import { ModuleMapLoaderModule } from &#39;@nguniversal/module-map-ngfactory-loader&#39;;<br>import { FlexLayoutServerModule } from &#39;@angular/flex-layout/server&#39;;<br><br>import { AppModule } from &#39;./app.module&#39;;<br>import { AppComponent } from &#39;./components/app/app.component&#39;;<br><br>@NgModule({<br>  imports: [<br>    AppModule,<br>    ServerModule,<br>    ModuleMapLoaderModule,<br>    ServerTransferStateModule,<br>    FlexLayoutServerModule,<br>  ],<br>  providers: [<br>    // Add universal-only providers here<br>  ],<br>  bootstrap: [AppComponent],<br>})<br>export class AppServerModule { }</pre><p>Add main.server.ts -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/src/main.server.ts">main.server.ts</a></pre><pre>export { AppServerModule } from &#39;./app/app.server.module&#39;;</pre><p>and modify our app.module to use server transition -</p><pre>// app.module.ts<br>import { BrowserModule, BrowserTransferStateModule } from &#39;@angular/platform-browser&#39;;<br>...</pre><pre>imports [<br>...<br>BrowserModule.withServerTransition({ appId: &#39;trivia&#39; }),<br>...</pre><p>We would then add a tsconfig.server.json that extends our client-side tsconfig -</p><pre>// tsconfig.server.json</pre><pre>{<br>  &quot;extends&quot;: &quot;../tsconfig.json&quot;,<br>  &quot;compilerOptions&quot;: {<br>    &quot;outDir&quot;: &quot;../out-tsc/app&quot;,<br>    &quot;baseUrl&quot;: &quot;./&quot;,<br>    &quot;module&quot;: &quot;commonjs&quot;,<br>    &quot;types&quot;: []<br>  },<br>  &quot;exclude&quot;: [<br>    &quot;test.ts&quot;,<br>    &quot;**/*.spec.ts&quot;<br>  ],<br>  &quot;angularCompilerOptions&quot;: {<br>    &quot;entryModule&quot;: &quot;app/app.server.module#AppServerModule&quot;<br>  }<br>}</pre><p>Also add a new build target to angular.json</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/angular.json">angular.json</a><br>...<br>&quot;server&quot;: {<br> &quot;builder&quot;: &quot;@angular-devkit/build-angular:server&quot;,<br> &quot;options&quot;:<br> {<br>    &quot;outputPath&quot;: &quot;functions/dist/server&quot;,<br>    &quot;main&quot;: &quot;src/main.server.ts&quot;,<br>    &quot;tsConfig&quot;: &quot;src/tsconfig.server.json&quot;<br> }<br>...</pre><p>And finally we add our webpack configuration -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/webpack.server.config.js">webpack.server.config.js</a></pre><pre>const path = require(&#39;path&#39;);<br>const webpack = require(&#39;webpack&#39;);<br><br><br><br>module.exports = {<br>  entry: { server: &#39;./server.ts&#39; },<br>  resolve: { extensions: [&#39;.js&#39;, &#39;.ts&#39;] },<br>  mode: &#39;none&#39;,<br>  target: &#39;node&#39;,<br>  externals: [/(node_modules|main(\\|\/)..*(\\|\/).js)/],<br>  output: {<br>    path: path.join(__dirname, `functions/server/functions`),<br>    filename: &#39;[name].js&#39;<br>  },<br>  module: {<br>    rules: [<br>      { test: /\.ts$/, loader: &#39;ts-loader&#39;, exclude: /^(?!.*\.spec\.ts$).*\.ts$/  },<br>      {<br>        test: /(\\|\/)@angular(\\|\/)core(\\|\/).+\.js$/,<br>        parser: { system: true }<br>      }<br>    ]<br>  },<br>  plugins: [<br>    new webpack.ContextReplacementPlugin(<br>      /(.+)?angular(\\|\/)core(.+)?/,<br>      path.join(__dirname, &#39;src&#39;), // location of your src<br>      {} // a map of your routes<br>    ),<br>    new webpack.ContextReplacementPlugin(<br>      /(.+)?express(\\|\/)(.+)?/,<br>      path.join(__dirname, &#39;src&#39;),<br>      {}<br>    )<br>  ]<br>};</pre><p>To build our server bundle we can use this -</p><pre>webpack --config webpack.server.config.js</pre><h4>SSR for Firebase functions</h4><p>However, in order to deploy our bundle to firebase functions, we need to take care of a few more things.</p><p>First, we will upgrade our functions to run on node version 8 by modifying our package.json. Node v8 will allow us to use ES6 style imports, that some of our dependent libraries use -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/functions/package.json">package.json</a></pre><pre>...<br>&quot;engines&quot;: {    &quot;node&quot;: &quot;8&quot;  },<br>...</pre><p>We’ll then create an ssr.ts, separate from our app.ts to host our server-side angular bundle (note that we’ve upgraded the memory required for our ssr) -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/functions/ssr.ts">ssr.ts</a></pre><pre>const ssrFunction = require(&#39;firebase-functions&#39;);<br>const ngApp = require(&#39;./server&#39;).app;<br><br>const runtimeOpts = {<br>  memory: &#39;512MB&#39;<br>}<br><br>exports.ssr = ssrFunction.runWith(runtimeOpts).https.onRequest(ngApp);</pre><p>Next, we’ll update our firebase.json so we can redirect our requests to the ssr version of our application -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/firebase.json">firebase.json</a></pre><pre>{<br>  &quot;hosting&quot;: {<br>    &quot;public&quot;: &quot;dist&quot;,<br>    &quot;rewrites&quot;: [<br>      {<br>        &quot;source&quot;: &quot;/app/**&quot;,<br>        &quot;function&quot;: &quot;app&quot;<br>      },   <br>      {<br>        &quot;source&quot;: &quot;**&quot;,<br>        &quot;function&quot;: &quot;ssr&quot;<br>      }<br>    ]<br>  }<br>}</pre><h4>Deployment to firebase</h4><blockquote>Although the firebase-cli supports deployment of multiple functions, I found out that we can deploy only one function at a time. Since, we have our original app function and now the new ssr function, we’ll use a bit of a hack to do this.</blockquote><p>We’ll rename our original functions/index.js to <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/functions/app-functions.js">functions/app-functions.js</a> and add a new <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/functions/ssr-functions.js">functions/ssr-functions.js</a> -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/functions/ssr-functions.js">ssr-functions.js</a></pre><pre>exports.ssr = require(&#39;./server/functions/ssr&#39;).ssr;</pre><p>At the time of deployment, we’ll copy the appropriate functions.js file as index.js and then deploy to firebase.</p><p>Let’s create a bunch of compilation and deployment handlers for our application in <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/package.json">package.json</a> -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/package.json">package.json</a><br>...</pre><pre>&quot;compile-functions&quot;: &quot;rmdir /s/q functions\\server &amp; tsc --project functions&quot;,</pre><pre>&quot;webpack:server&quot;: &quot;webpack --config webpack.server.config.js&quot;,</pre><pre>&quot;compile-ssr-functions&quot;: &quot;npm run compile-functions &amp;&amp; npm run webpack:server &amp;&amp; cpx dist/index.html functions/dist &amp;&amp; rimraf dist/index.html&quot;,</pre><pre>&quot;pre-compile-app-functions&quot;: &quot;rm -rf functions/index.js &amp;&amp; cp functions/app-functions.js functions/index.js&quot;,</pre><pre>&quot;pre-compile-ssr-functions&quot;: &quot;rm -rf functions/index.js &amp;&amp; cp functions/ssr-functions.js functions/index.js&quot;,</pre><pre>&quot;prod:build:ssr&quot;: &quot;ng build --prod --configuration=production &amp;&amp; ng run trivia:server&quot;,</pre><pre>&quot;prod:deploy-functions-build&quot;: &quot;npm run prod:build:ssr &amp;&amp; npm run compile-ssr-functions&quot;,</pre><pre>&quot;prod:deploy-functions-app&quot;: &quot;npm run pre-compile-app-functions &amp;&amp; firebase deploy -P production --only functions&quot;,</pre><pre>&quot;prod:deploy-functions-ssr&quot;: &quot;npm run pre-compile-ssr-functions &amp;&amp; firebase deploy -P production --only functions:ssr&quot;,</pre><pre>&quot;prod:set-index&quot;: &quot;firebase -P production functions:config:set elasticsearch.index.production=true&quot;,</pre><pre>&quot;prod:deploy-apps-to-firebase&quot;: &quot;firebase deploy -P production --only hosting&quot;,</pre><pre>&quot;<strong>prod:deploy-functions</strong>&quot;: &quot;npm run prod:deploy-functions-build &amp;&amp; npm run prod:set-index &amp;&amp; npm run prod:deploy-functions-app &amp;&amp; npm run prod:deploy-functions-ssr &amp;&amp; prod:deploy-apps-to-firebase&quot;,</pre><pre>...</pre><p>The prod:deploy-functions command will compile the ssr application &amp; to the task of deploying everything including the ssr &amp; app functions and the front-end hosting app.</p><blockquote>Note the removal of index.html from the client hosting app as firebase will not redirect to the server side index.html if it finds this file.</blockquote><p>And that’s all we need for deploying our SSR app to firebase functions. Let’s give it a try.</p><h4>Detecting server vs browser</h4><p>Not all functionality need be made available to our server-side application. For example, game-play, profile settings and pretty much any functionality that requires a user to login will remain a client-side function. In order to detect whether the app is running in a browser or on the server at runtime, , Angular provides PLATFORM_ID that can be checked in our code -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/src/app/core/auth/authentication.provider.ts">authentication.provider.ts</a> <br>...</pre><pre>constructor( ... <br>  @Inject(PLATFORM_ID) private platformId: Object,<br>...)</pre><pre>...</pre><pre>ensureLogin = function (url?: string) {<br>   if (!this.isAuthenticated) {<br>    if (isPlatformBrowser(this.platformId)) {<br>      this.showLogin(url);<br>      if (!this.isAuthenticated) {<br>        this.showLogin(url);<br>      }<br>    }<br>   }<br>};<br>...</pre><blockquote>Our web application is now a fully functional SSR app. If you do a view source on the browser, you’ll now see the entire html generated by angular universal rendered on the server.</blockquote><h4>SEO and social meta tags</h4><p>We take this opportunity to add some SEO specific headers and social tags to our <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/src/index.html">index.html </a>to help facebook &amp; twitter to better crawl our site.</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/src/index.html">index.html</a><br>...<br>&lt;head&gt;<br>...<br>  &lt;!-- Schema.org markup for Google+ --&gt;<br>  &lt;meta itemprop=&quot;name&quot; content=&quot;bitwiser.io: get wiser - bit by bit&quot;&gt;<br>  &lt;meta itemprop=&quot;description&quot; content=&quot;Open source tech trivia game. by, for and of the developers&quot;&gt;<br>  &lt;meta itemprop=&quot;image&quot; content=&quot;https://cdn-images-1.medium.com/max/1600/1*5RH1reNYpyBJOKauMMX2tw.png&quot;&gt;<br><br>  &lt;!-- Facebook Open graph --&gt;<br>  &lt;meta property=&quot;og:url&quot; content=&quot;https://bitwiser.io&quot; /&gt;<br>  &lt;meta property=&quot;og:type&quot; content=&quot;article&quot; /&gt;<br>  &lt;meta property=&quot;og:title&quot; content=&quot;bitwiser.io: get wiser - bit by bit&quot; /&gt;<br>  &lt;meta property=&quot;og:description&quot; content=&quot;Open source tech trivia game. by, for and of the developers&quot; /&gt;<br>  &lt;meta property=&quot;og:image&quot; content=&quot;https://cdn-images-1.medium.com/max/1600/1*5RH1reNYpyBJOKauMMX2tw.png&quot; /&gt;<br>  &lt;meta property=&quot;fb:app_id&quot; content=&quot;1436607329691182&quot; /&gt;<br>  &lt;meta property=&quot;fb:admins&quot; content=&quot;10156413230908290&quot; /&gt;<br>  &lt;!--meta property=&quot;og:site_name&quot; content=&quot;Site Name, i.e. Moz&quot; /--&gt;<br><br>  &lt;!-- Twitter Card data --&gt;<br>  &lt;meta name=&quot;twitter:card&quot; content=&quot;summary&quot;&gt;<br>  &lt;!--meta name=&quot;twitter:site&quot; content=&quot;@publisher_handle&quot;--&gt;<br>  &lt;meta name=&quot;twitter:title&quot; content=&quot;bitwiser.io: get wiser - bit by bit&quot;&gt;<br>  &lt;meta name=&quot;twitter:description&quot; content=&quot;Open source tech trivia game. by, for and of the developers&quot;&gt;<br>  &lt;!--meta name=&quot;twitter:creator&quot; content=&quot;@author_handle&quot;--&gt;<br>  &lt;!-- Twitter summary card with large image must be at least 280x150px --&gt;<br>  &lt;meta name=&quot;twitter:image:src&quot; content=&quot;https://cdn-images-1.medium.com/max/1600/1*5RH1reNYpyBJOKauMMX2tw.png&quot;&gt;<br>---<br>&lt;/head&gt;</pre><h4>Google analytics</h4><p>At this time, I also added google analytics to our site so we can start capturing more data about the user behavior. For now, we’ll just add the basic tag manager script so we can analyze the hits to the site. At a later point, we can add more specific data points for analyzing user behavior on our site.</p><p>For this, we must first go to google analytics and obtain a GA tracking code for our site. We can then plug this into our index.html -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/part-23/src/index.html">index.html</a><br>...</pre><pre>&lt;script async src=&quot;https://www.googletagmanager.com/gtag/js?id=UA-122807814-1&quot;&gt;&lt;/script&gt;<br>  &lt;script&gt;<br>    (function (i, s, o, g, r, a, m) {<br>      i[&#39;GoogleAnalyticsObject&#39;] = r;<br>      i[r] = i[r] || function () {<br>        (i[r].q = i[r].q || []).push(arguments)<br>      }, i[r].l = 1 * new Date();<br>      a = s.createElement(o),<br>        m = s.getElementsByTagName(o)[0];<br>      a.async = 1;<br>      a.src = g;<br>      m.parentNode.insertBefore(a, m)<br>    })(window, document, &#39;script&#39;, &#39;https://www.google-analytics.com/analytics.js&#39;, &#39;ga&#39;);<br>    ga(&#39;create&#39;, &#39;&lt;GA Tracking ID&gt;&#39;, &#39;auto&#39;);// add your tracking ID here.<br>    ga(&#39;send&#39;, &#39;pageview&#39;);<br>    window.dataLayer = window.dataLayer || [];<br>    function gtag() { dataLayer.push(arguments); }<br>    gtag(&#39;js&#39;, new Date());<br>    gtag(&#39;config&#39;, &#39;&lt;GA Tracking ID&gt;&#39;);<br>  &lt;/script&gt;</pre><p>Complete code for this part can be hound <a href="https://github.com/anihalaney/rwa-trivia/tree/part-23">here</a>.</p><h4>Alternates to Angular universal</h4><p>An alternate to SSR with angular universal is using a pre-renderer such as Rendertron (<a href="https://github.com/GoogleChrome/rendertron">https://github.com/GoogleChrome/rendertron</a>) or phantomJS (<a href="http://phantomjs.org/">http://phantomjs.org/</a>)</p><p>We can then check if the request header to see if the user-agent calling our page is a webcrawler bot and return the pre-rendered page instead of our client-side SPA. You can check an example of this here - <a href="https://github.com/anihalaney/rwa-trivia/blob/rendertron/functions/ssr.ts">https://github.com/anihalaney/rwa-trivia/blob/rendertron/functions/ssr.ts</a>.</p><h4>Next up</h4><p>In this part we saw how we can use Angular universal with Express/NodeJS to enable server-side rendering for our app. SSR is a key element of our application as it enables search engines and social media sites to discover and crawl our web application.</p><p>In the next part, we would see how we can use workspaces (introduced in Angular 6) to separate out the admin piece of our application into it’s own SPA. Workspace will also help us when we decide to create a separate mobile app as it’s own application.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing; <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">X: Upgrading to Angular 4</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">19: Two Player</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">20: Upgrading to Angular 6</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-21-service-workers-pwa-with-angular-3ba5c7168f3f">21: PWA</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-22-angular-testing-with-protractor-jasmine-and-jest-6a0e03a89038">22: e2e Testing</a></li></ul><h4>References</h4><ul><li><a href="https://angular.io/guide/universal">https://angular.io/guide/universal</a></li><li><a href="https://hackernoon.com/deploying-angular-universal-v6-with-firebase-c86381ddd445">https://hackernoon.com/deploying-angular-universal-v6-with-firebase-c86381ddd445</a></li><li><a href="https://medium.freecodecamp.org/what-exactly-is-client-side-rendering-and-hows-it-different-from-server-side-rendering-bd5c786b340d">https://medium.freecodecamp.org/what-exactly-is-client-side-rendering-and-hows-it-different-from-server-side-rendering-bd5c786b340d</a></li><li><a href="https://angular.io/guide/universal">https://angular.io/guide/universal</a></li><li><a href="https://dev.to/stereobooster/server-side-rendering-or-ssr-what-is-it-for-and-when-to-use-it-2cpg">https://dev.to/stereobooster/server-side-rendering-or-ssr-what-is-it-for-and-when-to-use-it-2cpg</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=637ec8490c44" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-23-ssr-with-angular-universal-637ec8490c44">Real World App - Part 23: SSR with Angular Universal</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 22: Angular Testing with Protractor, Jasmine and Jest]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-22-angular-testing-with-protractor-jasmine-and-jest-6a0e03a89038?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/6a0e03a89038</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[jasmine]]></category>
            <category><![CDATA[jest]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[protractor]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sun, 22 Jul 2018 17:09:00 GMT</pubDate>
            <atom:updated>2018-07-22T17:09:00.006Z</atom:updated>
            <content:encoded><![CDATA[<p>End to end testing with Protractor and unit tests with Jest</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VAuIh4dq6BxKDL8yXmGMTw.jpeg" /></figure><p><em>This is Part 22 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">Part 9</a> of the series, I had introduced unit tests to our app. Unfortunately due to time constraints I was not able to keep them up to date as we moved along with development. In this part, I would start fresh first by purging all unit tests and then adding a few unit and end to end tests. As we move along the development, I would incrementally add more tests in the future.</p><h4>Unit Tests with Jest</h4><p>Since the unit tests were outdated, I decided to first delete all the existing tests to avoid confusion. As we are starting fresh, we decided to go with <a href="https://jestjs.io/">Jest</a> - an open source testing platform from Facebook.</p><h4>Karma/Jasmine vs Jest</h4><p>One of the advantages of using Jest over Jasmine/Karma was speed. Tests with Jest are significantly faster (2.5x times faster).</p><p>Some other advantages of Jest include out of the box code coverage, snapshot testing, and ability to run tests in parallel. Unlike karma, Jest does not run in a browser, but instead uses <a href="https://github.com/jsdom/jsdom">jsdom</a>. See references for links.</p><h4>Jest with Angular</h4><p>Setting up jest with Angular for testing is a breeze. Simply install the dependencies -</p><pre>npm install --save-dev @types/jest jest jest-preset-angular</pre><p>To package.json, add a jest section -</p><pre>&quot;jest&quot;: {<br>  &quot;preset&quot;: &quot;jest-preset-angular&quot;,<br>  &quot;setupTestFrameworkScriptFile&quot;: &quot;&lt;rootDir&gt;/src/setup-jest.ts&quot;<br>}</pre><p>and the jest run commands -</p><pre>&quot;scripts&quot; : {<br>...<br>&quot;test&quot;: &quot;jest&quot;,<br>&quot;test:watch&quot;: &quot;jest --watch&quot;,<br>...<br>}</pre><p>Let’s also add the setup-jest.ts</p><pre>import &#39;jest-preset-angular&#39;;<br>import &#39;./jestGlobalMocks&#39;;</pre><p>And we’ll add our global mocks here to jestGlobalMocks -</p><pre>const mock = () =&gt; {<br>  let storage = {};<br>  return {<br>    getItem: key =&gt; key in storage ? storage[key] : null,<br>    setItem: (key, value) =&gt; storage[key] = value || &#39;&#39;,<br>    removeItem: key =&gt; delete storage[key],<br>    clear: () =&gt; storage = {},<br>  };<br>};</pre><pre>Object.defineProperty(window, &#39;localStorage&#39;, { value: mock() });<br>Object.defineProperty(window, &#39;sessionStorage&#39;, { value: mock() });<br>Object.defineProperty(window, &#39;getComputedStyle&#39;, {<br>  value: () =&gt; [&#39;-webkit-appearance&#39;]<br>});</pre><h4>Jest unit test</h4><p>You’ll see that tests written in Jest are very similar to the ones we wrote in Jasmine in <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">Part 9</a>. Here is a sample test file -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/0e3eb536c4e9f6c5306aba80115434eb6d75c596/src/app/social/components/newsletter/newsletter.component.spec.ts">newsletter.component.spec.ts</a></pre><pre>import { TestBed, ComponentFixture, async, fakeAsync } from &#39;@angular/core/testing&#39;;<br>import { ReactiveFormsModule, FormsModule } from &quot;@angular/forms&quot;;<br>import { NewsletterComponent } from &#39;./newsletter.component&#39;;<br>import { StoreModule, Store } from &#39;@ngrx/store&#39;;<br>import { User, Subscription } from &#39;../../../model&#39;;<br>import { TEST_DATA } from &#39;../../../testing/test.data&#39;;<br>import { subscribeOn } from &#39;rxjs/internal/operators/subscribeOn&#39;;<br><br>describe(&#39;Component: NewsletterComponent&#39;, async () =&gt; {<br><br>    let component: NewsletterComponent;<br>    let fixture: ComponentFixture&lt;NewsletterComponent&gt;;<br>    let _store: any;<br>    let spy: any;<br>    let user: User;<br><br>    beforeEach(() =&gt; {<br><br>        // refine the test module by declaring the NewsletterComponent component<br>        TestBed.configureTestingModule({<br>            imports: [ReactiveFormsModule, FormsModule, StoreModule.forRoot({})],<br>            providers: [Store],<br>            declarations: [NewsletterComponent]<br>        });<br><br>        // create component and NewsletterComponent fixture<br>        fixture = TestBed.createComponent(NewsletterComponent);<br><br>        // get NewsletterComponent component from the fixture<br>        component = fixture.componentInstance;<br>        component.user = user;<br><br>        // get the injected instances<br>        _store = fixture.debugElement.injector.get(Store);<br><br>        // get object of action<br>        spy = spyOn(_store, &#39;dispatch&#39;);<br><br>        // dispatch service to get TotalSubscriber count<br>        spy.and.callFake((action: any) =&gt; {<br>            expect(action.GetTotalSubscriber);<br>        });<br><br>        component.ngOnInit();<br><br>        expect(_store.dispatch).toHaveBeenCalled();<br><br>    });<br><br>    it(&#39;form invalid when empty&#39;, () =&gt; {<br>        expect(component.subscriptionForm.valid).toBeFalsy();<br>    });<br><br>    it(&#39;email field validity&#39;, () =&gt; {<br>        let errors = {};<br>        const email = component.subscriptionForm.controls[&#39;email&#39;];<br><br>        // Email field is required<br>        errors = email.errors || {};<br>        expect(errors[&#39;required&#39;]).toBeTruthy();<br><br>        // Set incorrect value to email<br>        email.setValue(&#39;test&#39;);<br>        errors = email.errors || {};<br>        expect(errors[&#39;required&#39;]).toBeFalsy();<br>        expect(errors[&#39;pattern&#39;]).toBeTruthy();<br><br>        // Set correct value to email<br>        email.setValue(&#39;demo@example.com&#39;);<br>        errors = email.errors || {};<br>        expect(errors[&#39;required&#39;]).toBeFalsy();<br>        expect(errors[&#39;pattern&#39;]).toBeFalsy();<br>    });<br><br>    it(&#39;subscription for normal user&#39;, () =&gt; {<br>        expect(component.subscriptionForm.valid).toBeFalsy();<br>        component.subscriptionForm.controls[&#39;email&#39;].setValue(&#39;test@test.com&#39;);<br>        expect(component.subscriptionForm.valid).toBeTruthy();<br><br>        // dispatch service to save subscribe email<br><br>        const subscription = new Subscription();<br>        subscription.email = component.subscriptionForm.controls[&#39;email&#39;].value;<br>        if (user) {<br>            subscription.userId = user.userId;<br>        }<br>        spy.and.callFake((action: any) =&gt; {<br>            expect(action.AddSubscriber);<br>            expect(action.payload.subscription).toEqual(subscription);<br>        });<br><br>        // Trigger the subscribe function<br>        component.onSubscribe();<br><br>        expect(_store.dispatch).toHaveBeenCalled();<br><br>        // Now we can check to make sure the emitted value is correct<br>        expect(component.subscriptionForm.get(&#39;email&#39;).value).toBe(&#39;test@test.com&#39;);<br>    });<br><br>    it(&#39;subscription for logged in user&#39;, () =&gt; {<br>        expect(component.subscriptionForm.valid).toBeFalsy();<br>        user = { ...TEST_DATA.userList[0] };<br>        component.user = user;<br>        component.subscriptionForm.controls[&#39;email&#39;].setValue(user.email);<br>        expect(component.subscriptionForm.valid).toBeTruthy();<br><br>        // dispatch service to save subscribe email<br><br>        const subscription = new Subscription();<br>        subscription.email = user.email;<br>        if (user) {<br>            subscription.userId = user.userId;<br>        }<br><br>        spy.and.callFake((action: any) =&gt; {<br>            expect(action.AddSubscriber);<br>            expect(action.payload.subscription).toEqual(subscription);<br>        });<br><br>        // Trigger the subscribe function<br>        component.onSubscribe();<br><br>        expect(_store.dispatch).toHaveBeenCalled();<br><br>        // Now we can check to make sure the emitted value is correct<br>        console.log(&quot;expected&quot; + JSON.stringify(user));<br>        expect(component.subscriptionForm.get(&#39;email&#39;).value).toBe(user.email);<br>        expect(component.user.userId).toBe(user.userId);<br>    });<br>});</pre><p>Complete code for this <a href="https://github.com/anihalaney/rwa-trivia/tree/part-22">part</a>.</p><p>To run the test, we can simply execute “npm run test”.</p><h4>End to End Testing with Protractor</h4><p>The cli has already configured our project for end to end testing with <a href="https://www.protractortest.org/#/">Protractor</a>, including the <a href="https://github.com/anihalaney/rwa-trivia/blob/part-22/e2e/tsconfig.e2e.json">tsconfig</a> and <a href="https://github.com/anihalaney/rwa-trivia/blob/part-22/protractor.conf.js">protractor.conf.js</a>.</p><p>In order to write e2e tests, we need 2 files.</p><p>A Page Object file that contains a class which describes a high level view of our component or page. This class contains logic to find all the elements on the component (and how to navigate to the page, if applicable) -</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/0e3eb536c4e9f6c5306aba80115434eb6d75c596/e2e/social/newsletter/newsletter.po.ts">newsletter.po.ts</a></pre><pre>import { browser, element, by, Key } from &#39;protractor&#39;;<br>export class NewsLetterPage {<br>    getTitle() {<br>        const titleElement = element(by.css(&#39;.subscrib h2&#39;));<br>        if (titleElement.isPresent) {<br>            return titleElement.getText();<br>        }<br>    }<br><br>    getSubTitle() {<br>        const subtitleElement = element(by.css(&#39;.subscrib p&#39;));<br>        if (subtitleElement.isPresent) {<br>            return subtitleElement.getText();<br>        }<br>    }<br><br>    getEmailElement() {<br>        return element(by.css(&#39;input[formControlName=email]&#39;));<br>    }<br><br>    getSubscribeButton() {<br>        return element(by.cssContainingText(&#39;button&#39;, &#39;Subscribe&#39;));<br>    }<br><br>    getRequiredMessage() {<br>        const errorElement = element(by.css(&#39;.errorSpan&#39;));<br>        if (errorElement.isPresent) {<br>            return errorElement.getText();<br>        }<br>    }<br><br>    getResponseMessage() {<br>        const responseElement = element(by.css(&#39;.subscrib-form p&#39;));<br>        if (responseElement.isPresent) {<br>            return responseElement.getText();<br>        }<br>    }<br>}</pre><p>An e2e test spec file using Jasmine to write our test cases. We first initialize our page object and use that to test the functionality of our page.</p><pre>// <a href="https://github.com/anihalaney/rwa-trivia/blob/0e3eb536c4e9f6c5306aba80115434eb6d75c596/e2e/social/newsletter/newsletter.e2e-spec.ts">newsletter.e2e-spec.ts</a></pre><pre>import { NewsLetterPage } from &#39;./newsletter.po&#39;;<br>import { browser, element, by, Key } from &#39;protractor&#39;;<br><br>describe(&#39;NewsLetterPage&#39;, () =&gt; {<br><br>    let page: NewsLetterPage;<br>    page = new NewsLetterPage();<br>    // page.navigateTo();<br><br>    it(&#39;Should display title&#39;, () =&gt; {<br>        browser.waitForAngularEnabled(false);<br>        expect(page.getTitle()).toMatch(&#39;Sign up for our newsletter to stay connected&#39;);<br>    });<br><br>    it(&#39;Should display subtitle&#39;, () =&gt; {<br>        browser.waitForAngularEnabled(false);<br>        expect(page.getSubTitle()).toMatch(&#39;Subscribe to our bi-weekly email newsletter for useful tips and valuable resources.&#39;);<br>    });<br><br>    it(&#39;Should check email validation by clicking Subscribe Button&#39;, () =&gt; {<br>        browser.waitForAngularEnabled(false);<br>        browser.driver.sleep(500);<br>        page.getSubscribeButton().click();<br>        expect(page.getRequiredMessage()).toMatch(&#39;Email is required!&#39;);<br>        browser.driver.sleep(2000);<br><br>        page.getEmailElement().sendKeys(&#39;test&#39;)<br>        page.getSubscribeButton().click();<br>        expect(page.getRequiredMessage()).toMatch(&#39;Invalid Email!&#39;);<br>        browser.driver.sleep(2000);<br><br>        page.getEmailElement().clear();<br>        browser.driver.sleep(500);<br>        page.getEmailElement().sendKeys(&#39;test@test.com&#39;);<br>        page.getSubscribeButton().click();<br>        expect(page.getResponseMessage()).toBeDefined();<br>        browser.driver.sleep(5000);<br>    });<br>});</pre><p>We can now test this using “npm run e2e”. Note that the site must be running on the port specified in our protractor.conf.js, prior to running the e2e tests.</p><p>Complete code for this <a href="https://github.com/anihalaney/rwa-trivia/tree/part-22">part</a>.</p><p>We have added some basic unit tests and e2e tests in this part. I would continue to add more tests as we touch various components as part of the continued</p><h4>Next Up</h4><p>Server side rendering</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">19: Two Player</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">20: Upgrading to Angular 6</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-21-service-workers-pwa-with-angular-3ba5c7168f3f">21: PWA</a>;</li></ul><h4>References:</h4><ul><li><a href="https://jestjs.io/">https://jestjs.io/</a></li><li><a href="https://semaphoreci.com/community/tutorials/testing-angular-2-and-continuous-integration-with-jest">https://semaphoreci.com/community/tutorials/testing-angular-2-and-continuous-integration-with-jest</a></li><li><a href="https://www.xfive.co/blog/testing-angular-faster-jest/">https://www.xfive.co/blog/testing-angular-faster-jest/</a></li><li><a href="https://izifortune.com/unit-testing-angular-applications-with-jest/">https://izifortune.com/unit-testing-angular-applications-with-jest/</a></li><li><a href="https://github.com/jest-community/vscode-jest">https://github.com/jest-community/vscode-jest</a></li><li><a href="https://coryrylan.com/blog/introduction-to-e2e-testing-with-the-angular-cli-and-protractor">https://coryrylan.com/blog/introduction-to-e2e-testing-with-the-angular-cli-and-protractor</a></li><li><a href="https://angular.io/guide/testing">https://angular.io/guide/testing</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6a0e03a89038" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-22-angular-testing-with-protractor-jasmine-and-jest-6a0e03a89038">Real World App - Part 22: Angular Testing with Protractor, Jasmine and Jest</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 21: Service Workers (PWA) with Angular]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-21-service-workers-pwa-with-angular-3ba5c7168f3f?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/3ba5c7168f3f</guid>
            <category><![CDATA[pwa]]></category>
            <category><![CDATA[service-worker]]></category>
            <category><![CDATA[angular-cli]]></category>
            <category><![CDATA[progressive-web-app]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Mon, 16 Jul 2018 11:01:01 GMT</pubDate>
            <atom:updated>2018-07-17T11:09:12.791Z</atom:updated>
            <content:encoded><![CDATA[<p>Service workers in Progressive Web Apps (PWA) with Angular</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ci5W1OycvqfaS1LbIYXFLw.jpeg" /></figure><p><em>This is Part 21 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In <a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">Part 20</a> of the series, we upgraded our app to Angular 6. In this part we’ll implement service workers to make our app a Progressive Web App. Final code for this part <a href="https://github.com/anihalaney/rwa-trivia/tree/part-21">here</a>.</p><h4>What are Progressive Web Apps?</h4><p>Progressive Web Apps (PWA) are apps built using a series of web technologies, patterns and APIs that allow a web application to appear like a native mobile app. For more details see - <a href="https://developer.mozilla.org/en-US/Apps/Progressive/Introduction">https://developer.mozilla.org/en-US/Apps/Progressive/Introduction</a></p><h4>Service Workers</h4><p>Service workers are an essential element of a PWA.</p><p>A service worker is type of a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">web worker</a> (executes script in a background thread separate from the main execution thread of a web application) that intercepts network requests, and can cache or retrieve resources from the cache. Service workers also allow delivering push notifications to the browser.</p><h4>Manifest file</h4><p>The web app manifest provides information about an application (such as its name, author, icon, and description) in a JSON text file. The manifest informs details for websites installed on the homescreen of a device, providing users with quicker access and a richer experience. In addition, Chrome on Android will proactively suggest that the user install the web app, via a <a href="https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android">web app install banner</a>.</p><blockquote>For benefits of PWA and details of service worker architecture check the links in the references section.</blockquote><h4>Service Workers in Angular</h4><p>Angular ships with service worker implementation since version 5. The cli makes adding service worker to your project really easy -</p><pre>ng add  @angular/pwa --project trivia</pre><p>The above command does the following:</p><ol><li>Adds the @angular/service-worker package to your project.</li><li>Enables service worker build support in the CLI.</li><li>Imports and registers the service worker in the app module.</li><li>Updates the index.html file:</li></ol><ul><li>Includes a link to add the manifest.json file.</li><li>Adds meta tags for theme-color.</li></ul><p>5. Installs icon files to support the installed Progressive Web App (PWA).</p><p>6. Creates the service worker configuration file called <a href="https://angular.io/guide/service-worker-config">ngsw-config.json</a>, which specifies the caching behaviors and other settings.</p><p>You can see the changes in <a href="https://github.com/anihalaney/rwa-trivia/pull/170/commits/e2cc27f39b22fe451e5a448a91edefa072f1dbd6">github</a>.</p><h4>manifest.json file</h4><p>A <a href="https://github.com/anihalaney/rwa-trivia/blob/part-21/src/manifest.json">manifest.json</a> file is created with name, icons and other settings. We can change this file as create new icons for our app as needed.</p><h4>Service worker configuration</h4><p>The <a href="https://github.com/anihalaney/rwa-trivia/blob/part-21/ngsw-config.json">ngsw-config.json</a> provides the configuration for service worker. It specifies the caching behavior of various assets used in the application with wild cards. When the app is built by the cli with prod flag, it will use this config and use this to generate ngsw.json file with names of each file it finds matching these wild cards. This json file is used as the run time configuration by the ngsw-worker.js</p><h4>Debugging with Chrome dev tools</h4><p>Once deployed with prod flag enabled, you can test the service worker in using Application tab in the Chrome dev tools.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N5fpr2LEaq0wjRoGjSKgjA.png" /></figure><p>You can see how the static assets are cached in Cache Storage. You can also try to take your app Offline and reload to see how the app behaves when the assets are cached.</p><p>You can also look at the manifest in Chrome dev tools -</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5WSygQz7miTDT8HNSjKAgQ.png" /></figure><h4>Testing with Chrome on Android</h4><p>When you test the app on Android using a Chrome browser, you‘ll see that Chrome prompts you to “Add to Home Screen”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6mNRu2-xhUdfwSez981GIA.png" /></figure><h4>Summary</h4><p>Angular provides an easy way to add service worker and PWA to our application. In addition to the option of adding to home screen on mobile, service workers provide several benefits such as caching for performance and offline support, push notifications, etc. We’ll also look at <a href="https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk?hl=en">lighthouse plugin</a> as a tool for testing our PWA.</p><p>In later parts we will revisit service workers and see how we can add push notifications and measure performance improvements and smoothen out the offline support. Complete code for this part, which also includes bug fixes and other minor enhancements <a href="https://github.com/anihalaney/rwa-trivia/tree/part-21">here</a>.</p><h4>Next Up</h4><p>In <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">Part 9</a>, we introduced unit testing, but I haven’t been able to keep the tests up to date. In the next part, I’ll start adding these back and also start with some end-to-end test cases.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">19: Two Player</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">20: Upgrading to Angular 6</a></li></ul><h4>References:</h4><ul><li><a href="https://developer.mozilla.org/en-US/Apps/Progressive/Introduction">https://developer.mozilla.org/en-US/Apps/Progressive/Introduction</a></li><li><a href="https://developers.google.com/web/fundamentals/primers/service-workers/">https://developers.google.com/web/fundamentals/primers/service-workers/</a></li><li><a href="https://developers.google.com/web/ilt/pwa/introduction-to-service-worker">https://developers.google.com/web/ilt/pwa/introduction-to-service-worker</a></li><li><a href="https://www.youtube.com/watch?v=jVfXiv03y5c">https://www.youtube.com/watch?v=jVfXiv03y5c</a></li><li><a href="https://angular.io/guide/service-worker-intro">https://angular.io/guide/service-worker-intro</a></li><li><a href="https://blog.angular-university.io/angular-service-worker/">https://blog.angular-university.io/angular-service-worker/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3ba5c7168f3f" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-21-service-workers-pwa-with-angular-3ba5c7168f3f">Real World App - Part 21: Service Workers (PWA) with Angular</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 20: Angular, ngrx & cli version 6]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/a3490b64f0c7</guid>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[angularfire2]]></category>
            <category><![CDATA[ngrx]]></category>
            <category><![CDATA[angular-cli]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Thu, 28 Jun 2018 10:23:48 GMT</pubDate>
            <atom:updated>2018-06-28T10:23:18.362Z</atom:updated>
            <content:encoded><![CDATA[<p>Upgrading Angular, cli, ngrx, material &amp; RxJS to version 6</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ICm02PB2vkKAkZid" /><figcaption>Photo by <a href="https://unsplash.com/@cliqueimages?utm_source=medium&amp;utm_medium=referral">Clique Images</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><em>This is Part 20 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In <a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">Part 19</a> of the series, we built out all the major functional pieces of our app. In this part, as we do from time to time, we’ll upgrade to the latest versions of our dependencies. Final code for this part <a href="https://github.com/anihalaney/rwa-trivia/tree/part-20">here</a>.</p><h4>What’s new?</h4><p><strong><em>Angular &amp; cli 6</em></strong></p><p>The Angular 6 update is less of an update in terms of the features, but more of an update of the underlying tool set around Angular. As part of the update, the angular team brought out updates to angular cli and material+cdk, while the ngrx team updated their project also to version 6. In addition we have a new version of RxJS (also 6, even though it’s version is not directly related to that of Angular).</p><p>New features of Angular includes -</p><ul><li>Schematics - Allows ease of development and updates to your dependencies</li><li>Elements - Allows you to ship Angular components as custom elements and use them in the browser without the framework dependency</li><li>Workspaces - Support for multiple workspaces in a single repo. The angular-cli.json is replaced with angular.json and supports build configuration of multiple workspaces</li></ul><p>A summary of changes can be seen here - <a href="https://blog.angular.io/version-6-of-angular-now-available-cc56b0efa7a4">https://blog.angular.io/version-6-of-angular-now-available-cc56b0efa7a4</a>. See references for more links on details.</p><p><strong><em>RxJS 6</em></strong></p><p>The two breaking changes introduced in RxJS are -</p><ul><li>Due to internal structure changes the import statements need to change</li><li>Use of pipe as a method to chain operators</li></ul><p>Fortunately, for the 6.x release RxJS provides rxjs-compat library that enables backward compatibility with the prior version with minimal code change. However, this will be deprecated with RxJS 7, so we’ll ensure everything is working without the compat library by the end of this part.</p><p>Let’s start our upgrade …</p><h4>Upgrading to 6</h4><p>We start by updating the global version of angular-cli by uninstalling the current version and then installing the latest 6.x.x. We will also update all our local packages to the latest versions (except for typescript - we’ll use the latest version currently supported by Angular 6 - 2.7.2).</p><p>We’ll then execute the ng update commands</p><pre>ng update @angular/cli<br>ng update @angular/core<br>ng update</pre><h4>RxJS (compat)</h4><p>In order to not modify all the operators, I used rxjs-compat so we can get everything to compile first</p><pre>npm install --save rxjs-compat</pre><p>When I tried to compile this, I got a bunch of errors on using ngrx select, when selecting from a slice of the store. So, everywhere we used the “select“ from the stores’ slice had to be changed as follows -</p><pre>this.store.select(adminState).select(s =&gt; s.questionsSearchResults);</pre><p>to a pipeable select. Note, we had to import select from ngrx/store</p><pre>import { select } from &#39;@ngrx/store&#39;;<br>...<br>this.questionsSearchResultsObs = this.store.select(adminState).pipe(select(s =&gt; s.questionsSearchResults));</pre><h4>csv parser</h4><p>After resolving the above I was still getting a Typescript error on the csv parser - Module not found: Error: Can’t resolve ‘stream’ in csv-parse. The csv parser library we were using (csv) was essentially a NodeJs library. We decided to remove our dependency on this and instead use the papaparse library</p><pre>npm uninstall csv<br>npm install papaparse</pre><p>And changed the code to use this library</p><h4>Angularfire2</h4><p>The getDownloadUrl() method on AngularFireUploadTask was removed, instead we have to subscribe to the snapshotChanges observable on finalize. (<a href="https://github.com/angular/angularfire2/blob/master/docs/storage/storage.md#monitoring-upload-percentage">https://github.com/angular/angularfire2/blob/master/docs/storage/storage.md#monitoring-upload-percentage</a>) This needed to be changed -</p><pre>const socialShareImageObj = this.storage.upload(`${this.basePath}/${userId}/${this.folderPath}/${new Date().getTime()}`, imageBlob);<br>return socialShareImageObj.downloadURL().map(url =&gt; url);</pre><p>was changed to -</p><pre>const filePath = `${this.basePath}/${userId}/${this.folderPath}/${new Date().getTime()}`;<br>const fileRef = this.storage.ref(filePath);</pre><pre>const socialShareImageObj = this.storage.upload(filePath, imageBlob);<br>return socialShareImageObj.snapshotChanges().pipe(<br>  finalize(() =&gt; fileRef.getDownloadURL())<br>);</pre><p>Finally, I was able to compile after resolving these.</p><h4>Runtime errors (Angular Material)</h4><p>After resolving all the compile errors, I thought that was it, but when trying to execute, I started getting some runtime errors -</p><p>Uncaught Error: Template parse errors:<br>‘mat-input-container’ is not a known element:</p><p>From <a href="https://github.com/angular/material2/blob/master/CHANGELOG.md#600-beta5-2018-03-23">cli change log</a>: The following deprecated CSS classes have been removed:</p><ul><li>mat-input-container instead use mat-form-field</li></ul><p>So, I changed all occurrences of “mat-input-container”s to “mat-form-field”. (I later realized, I could’ve used “ng update @angular/material” and that should’ve taken care of this issue. Oh well!)</p><p>And that was it! Everything worked again!</p><h4>RxJs (removing rxjs-compat)</h4><p>Finally, I removed the dependency on rxjs-compat and fixed all the imports and then operators to use pipes. Excellent concise article here — <a href="https://www.academind.com/learn/javascript/rxjs-6-what-changed/">https://www.academind.com/learn/javascript/rxjs-6-what-changed/</a></p><p>Final code on the <a href="https://github.com/anihalaney/rwa-trivia/tree/part-20">repo</a>.</p><p>We’re getting pretty close to a working system and fixing bugs as we go along.</p><h4>Next Up</h4><p>PWA / Service Workers.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">19: Two Player</a></li></ul><h4>References</h4><p><em>Angular 6</em></p><ul><li><a href="https://blog.angular.io/version-6-of-angular-now-available-cc56b0efa7a4">https://blog.angular.io/version-6-of-angular-now-available-cc56b0efa7a4</a></li><li><a href="https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2">https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2</a></li><li><a href="https://angular.io/guide/elements">https://angular.io/guide/elements</a></li><li><a href="https://github.com/angular/angular-cli/wiki/angular-workspace">https://github.com/angular/angular-cli/wiki/angular-workspace</a></li></ul><p><em>RxJS 6</em></p><ul><li><a href="https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md">https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md</a></li><li><a href="https://auth0.com/blog/whats-new-in-rxjs-6/">https://auth0.com/blog/whats-new-in-rxjs-6/</a></li><li><a href="https://www.academind.com/learn/javascript/rxjs-6-what-changed/">https://www.academind.com/learn/javascript/rxjs-6-what-changed/</a></li></ul><p><em>AngularFire2</em></p><ul><li><a href="https://github.com/angular/angularfire2/blob/master/docs/storage/storage.md#monitoring-upload-percentage">https://github.com/angular/angularfire2/blob/master/docs/storage/storage.md#monitoring-upload-percentage</a></li></ul><p>Angular Material 6</p><ul><li><a href="https://github.com/angular/material2/blob/master/CHANGELOG.md">https://github.com/angular/material2/blob/master/CHANGELOG.md</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3490b64f0c7" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-20-angular-ngrx-cli-version-6-a3490b64f0c7">Real World App - Part 20: Angular, ngrx &amp; cli version 6</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 19: Ready Player Two]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/9e17c2e7c694</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[firebase-cloud-functions]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sun, 24 Jun 2018 03:13:06 GMT</pubDate>
            <atom:updated>2018-06-24T03:12:46.337Z</atom:updated>
            <content:encoded><![CDATA[<p>Two player game play, computing stats, dashboard and other features.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QukM1mShWq5fyZJdspmEeg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/photos/sGJ8yIWlJUw?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Bernd Viefhues</a> on <a href="https://unsplash.com/search/photos/fight?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><em>This is Part 19 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">Part 18</a> of the series, we upgraded to ngrx 5. In this part we will focus on adding several features to our application including two player game play, revising our single player mode, revamping our dashboard, fixing bulk upload, stats and leaderboard and much more. In essence it will be a feature complete beta version.</p><p>We will also do a major refactor of our firebase functions codebase. Since this is essentially a feature rich part of development, I’ll discuss the features here rather than the code itself. Check out the code for this part <a href="https://github.com/anihalaney/rwa-trivia/tree/part-19">here</a>.</p><h4>Firebase functions</h4><p>As the functionality of our firebase functions was growing it became increasingly unmanageable to have a single app.ts file for all http functions. So we decided to break down our app into a traditional express app with routes, controllers, middleware and services. This gave us a set of manageable smaller modules. The new functions folder can be seen here - <a href="https://github.com/anihalaney/rwa-trivia/tree/part-19/functions">https://github.com/anihalaney/rwa-trivia/tree/part-19/functions</a>.</p><h4>Game Play mechanics</h4><p><strong><em>Two player</em></strong></p><p>A two player game could be played either with a random player or a friend. Once the game is started with a random player, the system will try to match you with another player who has also started the game at around the same time. With each correct question the player earns a badge. When a player answers incorrectly, the turn goes to the other player. The first to earn 5 badges wins the game. The game can last up to 16 rounds. If no one wins 5 badges in 16 rounds, then the game ends in a tie. Each player has 32 hours to play their turn, else the player defaults on the game.</p><p>Right now, the mechanism to earn a badge and random player selection are pretty basic. As we grow in content (questions and answers) and broaden the user base, we will incorporate a more intelligent match between random players.</p><p><strong><em>Single player</em></strong></p><p>The single player game play was also modified to be in line with the 2 player for consistency, albeit a much simpler version of it. 5 correct answers will win 5 badges and the game. Answer 3 questions incorrectly and you lose the game.</p><p>The game play mechanism involves significant code changes both to the front-end and firebase functions. A schedule job also checks for the 32 hour turn limit and marks the game over flag if applicable.</p><p>The dashboard is changed to reflect the badges &amp; rounds.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5RH1reNYpyBJOKauMMX2tw.png" /><figcaption>Dashboard</figcaption></figure><h4>Friends</h4><p>Invitation to friends can be send by entering their email address. We currently use gmail smtp to send invitations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xAiwQRUByWeXfuuJ_wiBwg.png" /><figcaption>Invite Friends</figcaption></figure><h4>Stats &amp; Leaderboard</h4><p>Stats are computed at a game level, user level and at system level. We use firebase triggers to compute these stats. See code <a href="https://github.com/anihalaney/rwa-trivia/blob/part-19/functions/db/firebase-functions.ts">here</a>. The top 10 category leaders are shown on the dashboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B5NPx8HT9yRCr4gs2xiw5A.png" /><figcaption>Leaderboard</figcaption></figure><h4>Blog links</h4><p>The links to blogs are acquired using a firebase function that fetches blogs from an rss feed. In our case the feed link is <a href="https://blog.realworldfullstack.io/feed">https://blog.realworldfullstack.io/feed</a>. The function extracts the top 3 latest blog and the relevant fields from the feed and stores it in the database. The firebase function is called on a scheduler. Code <a href="https://github.com/anihalaney/rwa-trivia/blob/part-19/functions/controllers/general.controller.ts">here</a>.</p><h4>Social Share</h4><p>Social share buttons are now active on the site. However, since our code is all client-side, the social sites are unable to render these when the page is shared. We will fix that with Angular universal and server-side rendering in our future posts.</p><p>This part has by far the biggest feature set we have done in our series, which is why the code changes are significant, even though we haven’t introduced much new technical concepts. It is also the reason why I haven’t posted an update in a long time. I’ll try to post smaller incremental updates from now on.</p><h4>Next Up</h4><p>Upgrade to Angular 6.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">18: Revisiting ngrx</a></li></ul><h4>References</h4><ul><li><a href="https://www.terlici.com/2014/08/25/best-practices-express-structure.html">https://www.terlici.com/2014/08/25/best-practices-express-structure.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9e17c2e7c694" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-19-ready-player-two-9e17c2e7c694">Real World App - Part 19: Ready Player Two</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 18: Revisiting ngrx]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/e20feed6312c</guid>
            <category><![CDATA[single-page-applications]]></category>
            <category><![CDATA[router]]></category>
            <category><![CDATA[ngrx]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[rxjs]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sun, 25 Mar 2018 14:33:01 GMT</pubDate>
            <atom:updated>2018-06-29T02:37:02.793Z</atom:updated>
            <content:encoded><![CDATA[<p>ngrx 5 - splitting store into feature modules, using action classes, selectors &amp; router-store</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Asoode5SyOKR-SmF5kBINg.jpeg" /></figure><p><em>This is Part 18 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io">https://bitwiser.io</a> and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In <a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">Part 4</a> of the series, I explained how we managed state of our application using ngrx. ngrx has undergone quite a few changes since and we’ll upgrade our application to reflect on those changes.</p><p>In this part we will upgrade our app to improve our state management using ngrx 5 and the new features it provides</p><p>In upgrading our application store to ngx 5 we will look at the following features -</p><ul><li>Using selectors</li><li>Split up store into feature modules</li><li>Replace action functions with action classes</li><li>Using pipeable functions in effects</li><li>router-store</li><li>Router actions in effects</li><li>Use of entities (as needed)</li></ul><h4>Using selectors</h4><p>Selectors are methods used for obtaining slices of store state.</p><p>A couple of links that explain it in depth -</p><ul><li><a href="https://github.com/ngrx/platform/blob/master/docs/store/selectors.md">https://github.com/ngrx/platform/blob/master/docs/store/selectors.m</a></li><li><a href="https://toddmotto.com/ngrx-store-understanding-state-selectors">https://toddmotto.com/ngrx-store-understanding-state-selectors</a></li></ul><p>I would start by refactoring categoryDictionary, making it a selector, instead of a reducer -</p><pre>// categories.reducer.ts</pre><pre>...<br>export const getCategoryDictionary = (state: Category[]) =&gt; {<br>  let categoryDict: {[key: number]: Category} = {};<br>    state.forEach(category =&gt; {<br>    categoryDict[category.id] = category;<br>  });<br>  return categoryDict;<br>}<br>...</pre><p>and in app-store.ts</p><pre>// app-store.ts</pre><pre>...<br>export const getState = (state: AppStore) =&gt; state;</pre><pre>//Categories selector<br>export const getCategories = createSelector(getState, (state: AppStore) =&gt; state.categories);<br>export const categoryDictionary = createSelector(getCategories, getCategoryDictionary);<br>...</pre><p>We can then refer to the selector by simply using <em>store.select(categoryDictionary)</em></p><p>Code for this <a href="https://github.com/anihalaney/rwa-trivia/commit/b6df0eec43febd33910bf5f4546ce731a3f4c2ee">commit</a>.</p><h4>Split up store into feature modules</h4><p>ngrx 2 allowed us to have the state of our application in a central store, but didn’t have any easy mechanism to split that into feature modules.</p><p>This limitation meant we had to put our entire store in our root module, instead of placing it in relevant modules, even for lazy loaded features where at times we may never need those slices of the store to be loaded in our app. We start by moving the game-play part of the store in the game-play module.</p><p>I’ll not go into details of the code as most of it is simply moving the relevant store parts from core feature to game-play feature. A couple of things to note -</p><p>The game-play.module.ts will create a StoreModule for feature</p><pre>// game-play.module.ts<br>...</pre><pre>  imports: [ ...</pre><pre>//ngrx feature store<br>    StoreModule.forFeature(&#39;gameplay&#39;, reducer),</pre><pre>//ngrx effects<br>    EffectsModule.forFeature(effects),</pre><pre>],<br>...</pre><p>And we create a feature selector in app-store</p><pre>//app-store.ts<br>...<br>export const gameplayState = createFeatureSelector&lt;GamePlayState&gt;(&#39;gameplay&#39;);<br>...</pre><p>We can then refer to our gameplayState using -&gt;</p><pre>this.gameObs = store.select(gameplayState).select(s =&gt; s.currentGame)</pre><p>Complete code for this <a href="https://github.com/anihalaney/rwa-trivia/commit/c3b805cc8d38c8bb30d396aeba3efd30b59615b4">commit</a>.</p><h4>Replace action functions with action classes</h4><p>The payload property was removed from the Action interface as it was a source of type-safety issues. We had temporarily gotten around this by providing a ActionWithPayload interface in <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">Part 14</a>.</p><p>Here we completely replace these functions with classes and use the constructor to provide the payload.</p><pre>//game-play.actions.ts<br>...<br>export enum GamePlayActionTypes {<br>  RESET_NEW = &#39;[GamePlay] ResetNew&#39;,<br>  CREATE_NEW = &#39;[GamePlay] CreateNew&#39;,<br>...<br>}</pre><pre>export class ResetNewGame implements Action {<br>  readonly type = GamePlayActionTypes.RESET_NEW;<br>  payload = null;<br>}</pre><pre>export class CreateNewGame implements Action {<br>  readonly type = GamePlayActionTypes.CREATE_NEW;<br>  constructor(public payload: {gameOptions: GameOptions, user: User}) {}<br>}<br>...<br>export type GamePlayActions<br>  = ResetNewGame<br>  | CreateNewGame<br>...</pre><p>and we use it by creating a new instance -</p><pre>this.store.dispatch(new gameplayactions.CreateNewGame({gameOptions: gameOptions, user: user}))</pre><p>Complete code for the <a href="https://github.com/anihalaney/rwa-trivia/commit/c3b805cc8d38c8bb30d396aeba3efd30b59615b4">commit</a>.</p><h4>Using pipeable operators in effects</h4><p>While pipeable operators are a new RxJs feature, rather than an ngrx feature, we use the opportunity to refactor our effects to use these operators.</p><p>Pipeable operators offer a mechanism of composing observable chains by using pure functions that can be used as standalone operators instead of methods on an observable. They’re lightweight and can decrease your overall build size by only including the operators being used. See reference section for further reading on this.</p><p>Sample effect -</p><pre>//game-play.effects.ts<br>...<br>@Effect() <br>  startNewGame$ = this.actions$<br>      .ofType(GamePlayActionTypes.CREATE_NEW)<br>      .pipe(<br>        switchMap((action: gameplayactions.CreateNewGame) =&gt; <br>          this.svc.createNewGame(action.payload.gameOptions, action.payload.user).pipe(<br>            map((gameId: string) =&gt; new gameplayactions.CreateNewGameSuccess(gameId))<br>            //catchError(error =&gt; new)<br>          )<br>        )<br>      );<br>...</pre><h4>router-store</h4><p>Our application currently stores only the domain entities in the store. The router-store allows us to store the url and the router state in the store based on a route change action on the angular router. We can then have effects listen to these actions and invoke our services to load data as needed. The components would no longer need to listen to route changes, but simply observe on the store for changes.</p><p>A great explanation of the states of our application is covered by <a href="https://medium.com/u/76fc1db4149b">Victor Savkin</a> in his post - <a href="https://blog.nrwl.io/using-ngrx-4-to-manage-state-in-angular-applications-64e7a1f84b7b">https://blog.nrwl.io/using-ngrx-4-to-manage-state-in-angular-applications-64e7a1f84b7b</a>.</p><p>The video series by Todd Motto listed in the reference section is also a great resource to understanding how this all comes together.</p><p>Let’s start by adding the router state and a custom serializer to our reducer -</p><pre>//reducers/index.ts<br>import ...</pre><pre>export interface RouterStateUrl {<br>  url: string;<br>  queryParams: Params;<br>  params: Params;<br>}<br>export interface State {<br>  routerReducer: fromRouter.RouterReducerState&lt;RouterStateUrl&gt;<br>}</pre><pre>export const reducers: ActionReducerMap&lt;State&gt; = {<br>  routerReducer: fromRouter.routerReducer<br>}</pre><pre>export const routerState = <br>  createFeatureSelector&lt;fromRouter.RouterReducerState&lt;RouterStateUrl&gt;&gt;(&#39;routerReducer&#39;);</pre><pre>export class CustomSerializer implements fromRouter.RouterStateSerializer&lt;RouterStateUrl&gt; {<br>  <br>  serialize(routerState: RouterStateSnapshot): RouterStateUrl {<br>    const {url} = routerState;<br>    const {queryParams} = routerState.root;</pre><pre>  let state: ActivatedRouteSnapshot = routerState.root;<br>    while (state.firstChild) {<br>      state = state.firstChild;<br> }</pre><pre>    const {params} = state;<br>    return { url, queryParams, params };<br>  }<br>}</pre><p>We can then list our CustomSerializer as a provider for RouterStateSerializer in the app.module.</p><h4>Using router actions in effects</h4><p>We can now observe on these router actions in our effects to load data.</p><pre>//game.play.effects.ts<br>...<br>  //load from router<br>  @Effect()<br>  // handle location update<br>  loadGame$ = this.actions$<br>    .ofType(&#39;ROUTER_NAVIGATION&#39;)<br>    .map((action: any): RouterStateUrl =&gt; action.payload.routerState)<br>    .filter((routerState: RouterStateUrl) =&gt; <br>      routerState.url.toLowerCase().startsWith(&#39;/game-play/&#39;) &amp;&amp; <br>      routerState.params.gameid<br>    )<br>    .pipe(<br>      switchMap((routerState: RouterStateUrl) =&gt;<br>        this.svc.getGame(routerState.params.gameid).pipe(<br>          map((game: Game) =&gt; new gameplayactions.LoadGameSuccess(game))<br>        )<br>      )<br>    );<br>...</pre><p>As we can see above, we can look for the game-play route to load our game. We can remove the dispatch action for the route from our component.</p><p>We’ll go ahead and implement these changes across the application. We’ll add stores for core and bulk feature modules. The complete code for this part is <a href="https://github.com/anihalaney/rwa-trivia/tree/part-18">here</a>.</p><p>In this part, we refactored our store into smaller feature module stores and used the router-store to manage our route states. We also fixed some bugs and made UI changes to cleanup our bulk upload.</p><h4>Next Up</h4><p>Two player game play.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">17: Firebase Cloud Storage</a></li></ul><h4>References</h4><p><em>ngrx</em></p><ul><li><a href="https://github.com/ngrx/platform">https://github.com/ngrx/platform</a></li><li><a href="https://github.com/ngrx/platform/blob/master/MIGRATION.md">https://github.com/ngrx/platform/blob/master/MIGRATION.md</a></li><li><a href="https://blog.nrwl.io/ngrx-patterns-and-techniques-f46126e2b1e5">https://blog.nrwl.io/ngrx-patterns-and-techniques-f46126e2b1e5</a></li><li><a href="https://blog.nrwl.io/using-ngrx-4-to-manage-state-in-angular-applications-64e7a1f84b7b">https://blog.nrwl.io/using-ngrx-4-to-manage-state-in-angular-applications-64e7a1f84b7b</a></li><li><a href="https://medium.com/ngrx/ngrx-5-and-schematics-2d8cc3906506">https://medium.com/ngrx/ngrx-5-and-schematics-2d8cc3906506</a></li><li><a href="https://github.com/ngrx/platform/blob/master/docs/router-store/api.md">https://github.com/ngrx/platform/blob/master/docs/router-store/api.md</a></li></ul><p><em>Excellent video on ngrx</em></p><ul><li><a href="https://www.youtube.com/watch?v=N_UQx8dPPkc&amp;list=PLW2eQOsUPlWJRfWGOi9gZdc3rE4Fke0Wv">https://www.youtube.com/watch?v=N_UQx8dPPkc&amp;list=PLW2eQOsUPlWJRfWGOi9gZdc3rE4Fke0Wv</a></li></ul><p><em>Pipeable operators</em></p><ul><li><a href="https://blog.hackages.io/rxjs-5-5-piping-all-the-things-9d469d1b3f44">https://blog.hackages.io/rxjs-5-5-piping-all-the-things-9d469d1b3f44</a></li><li><a href="https://blog.angularindepth.com/rxjs-understanding-lettable-operators-fe74dda186d3">https://blog.angularindepth.com/rxjs-understanding-lettable-operators-fe74dda186d3</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e20feed6312c" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-18-revisiting-ngrx-e20feed6312c">Real World App - Part 18: Revisiting ngrx</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 17: Cloud storage with Firebase and Angular]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/d3d2c9f5f27c</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[google-cloud-storage]]></category>
            <category><![CDATA[angularfire2]]></category>
            <category><![CDATA[firebase]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Sun, 11 Mar 2018 21:40:20 GMT</pubDate>
            <atom:updated>2018-06-29T02:38:08.238Z</atom:updated>
            <content:encoded><![CDATA[<p>Adding features: Bulk upload and User profile settings using firebase cloud storage</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a0Z6jsHuF8ASw089hSgnIw.jpeg" /></figure><p><em>This is Part 17 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io">https://bitwiser.io</a> and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In the <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">previous post</a>, we migrated our data from Firebase realtime database (<strong><em>rtdb</em></strong>) to firestore.</p><p>In this part, we’ll add a couple of features to the application. The bulk upload feature will allow select users upload question/answer content in bulk instead of doing it one at a time. We’ll also add an initial version of user profile settings.</p><h4>Bulk upload feature</h4><p>The bulk upload feature will allow users (with appropriate role) to upload a csv file with Question and Answers in a batch. The file will get saved into the firebase storage and the data will be imported into the unpublished questions. The admin will then be able to verify them, approve/reject or send it back to be revised.</p><p>The instructions to upload are shown below and the material stepper control will be used for uploading the file. We’re restricting the file to have only one category at a time. If a user wants to upload Qs for multiple categories, they must do it one file at a time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BtfNWCTHl47scYT2svHRAQ.jpeg" /></figure><h4>User Profile feature</h4><p>The user profile allows the user their basic profile info. The user profile will also need firebase storage to store avatar images of the user.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bS-NTbZCRDVQjM8asYdtsg.jpeg" /></figure><h4>Cloud storage</h4><p>Cloud Storage stores your files in a Google Cloud Storage bucket, making them accessible through both Firebase and Google Cloud. This allows you the flexibility to upload and download files from mobile clients via the Firebase SDKs, and do server-side processing such as image filtering or video transcoding using Google Cloud Platform.</p><h4>Firebase Cloud storage security rules</h4><p>The firebase storage rules works similar to the firestore database. Refer to the documentation for an understanding the security rules - <a href="https://firebase.google.com/docs/storage/security/">https://firebase.google.com/docs/storage/security/</a></p><p>We’ll start initially by allowing all authenticated users to read/write, but later setup so the users can only see their files -</p><pre>service firebase.storage {<br>  match /b/{bucket}/o {<br>    match /{allPaths=**} {<br>      allow read, write: if request.auth != null;<br>    }<br>  }<br>}</pre><p>For final rules for this part see <a href="https://github.com/anihalaney/rwa-trivia/blob/part-17/firestorage-rules.txt">here</a></p><h4>Upgrading angularfire2</h4><p>As always, we will upgrade our components to the latest versions.</p><blockquote>NOTE: The support for firebase storage sdk was introduced in version 5.0.0-rc.6</blockquote><p>This part has large amount of code change, but a most of it is wiring up our components, store and services that we’ve seen enough times in this series. The complete source for this part is <a href="https://github.com/anihalaney/rwa-trivia/blob/part-17">here</a>.</p><p>We’ll simply focus on code related to storage and file uploads.</p><blockquote>Make sure to include the “AngularFireStorageModule” in our core.module.ts</blockquote><p>The following function in <a href="https://github.com/anihalaney/rwa-trivia/blob/part-17/src/app/core/services/question.service.ts">question.service.ts</a> uploads the file to the storage. The file is created under the users uid node, so we can use the security rules appropriately). It also has a unique id as part of it’s name, that will be be used to refer it back from the questions saved in the firestore db.</p><pre>//question.service.ts</pre><pre>...<br>saveBulkQuestions(bulkUpload: BulkUpload) {<br>    const dbQuestions: Array&lt;any&gt; = [];<br>    const bulkUploadFileInfo = bulkUpload.bulkUploadFileInfo;<br>    const questions = bulkUpload.questions;</pre><pre>const bulkUploadId = this.db.createId();<br>    // store file in file storage<br>    // Not written any code monitor progress or error<br>    this.storage.upload(`bulk_upload/${bulkUploadFileInfo.created_uid}/${bulkUploadId}-${bulkUpload.file.name}`, bulkUpload.file);<br>    for (const question of questions) {<br>      if (question !== null) {<br>        question.bulkUploadId = bulkUploadId;<br>        const dbQuestion = Object.assign({}, question); // object to be saved<br>        dbQuestion.id = this.db.createId();<br>        // Do we really need to copy answer object as well?<br>        dbQuestion.answers = dbQuestion.answers.map((obj) =&gt; { return Object.assign({}, obj) });<br>        dbQuestions.push(dbQuestion);<br>      }<br>    }<br>    this.addBulkUpload(bulkUploadFileInfo, dbQuestions, bulkUploadId);<br>  }</pre><pre>...</pre><h4>bulk upload component</h4><p>The <a href="https://github.com/anihalaney/rwa-trivia/blob/part-17/src/app/bulk/components/bulk-upload/bulk-upload.component.ts">bulk-upload.component</a> incorporates the logic for parsing out the csv, validating it and displaying the questions back to the user before submitting it. Partial code below -</p><pre>//bulk-upload.component.ts</pre><pre>...</pre><pre>onFileChange(event) {<br>    const reader = new FileReader();<br>    this.fileParseError = false;<br>    if (event.target.files &amp;&amp; event.target.files.length &gt; 0) {<br>      const file = this.file = event.target.files[0];<br>      this.uploadFormGroup.get(&#39;csvFile&#39;).setValue(file);<br>      reader.readAsText(file);<br>      reader.onload = () =&gt; {<br>        this.bulkUploadFileInfo = new BulkUploadFileInfo;<br>        this.bulkUploadFileInfo.fileName = file[&#39;name&#39;];<br>        this.generateQuestions(reader.result);<br>      };<br>    }</pre><pre>}</pre><pre>generateQuestions(csvString: string): void {<br>    this.questionValidationError = false;<br>    this.fileParseError = false;<br>    this.fileParseErrorMessage = &#39;&#39;;</pre><pre>const parseOptions = {<br>      &#39;columns&#39;: columns =&gt; {<br>        const validColumns = [&#39;Question&#39;, &#39;Option 1&#39;, &#39;Option 2&#39;, &#39;Option 3&#39;, &#39;Option 4&#39;, &#39;Answer Index&#39;, &#39;Tag 1&#39;, &#39;Tag 2&#39;, &#39;Tag 3&#39;,<br>          &#39;Tag 4&#39;, &#39;Tag 5&#39;, &#39;Tag 6&#39;, &#39;Tag 7&#39;, &#39;Tag 8&#39;, &#39;Tag 9&#39;];<br>        if (validColumns.join(&#39;,&#39;) === columns.join(&#39;,&#39;)) {<br>          return columns;<br>        } else {<br>          this.fileParseError = true;<br>          this.fileParseErrorMessage = &#39;File format is not correct, must be in CSV format, must not have missing or wrong column order&#39;;<br>          return &#39;&#39;;<br>        }<br>      },<br>      &#39;skip_empty_lines&#39;: true<br>    };</pre><pre>parse(csvString, parseOptions,<br>      (err, output) =&gt; {<br>        if (output !== undefined &amp;&amp; output !== &#39;&#39;) {<br>          this.questions =<br>            output.map(element =&gt; {<br>              const question: Question = new Question();<br>              question.questionText = element[&#39;Question&#39;];<br>              question.answers = [<br>                { &#39;id&#39;: 1, &#39;answerText&#39;: element[&#39;Option 1&#39;], correct: false },<br>                { &#39;id&#39;: 2, &#39;answerText&#39;: element[&#39;Option 2&#39;], correct: false },<br>                { &#39;id&#39;: 3, &#39;answerText&#39;: element[&#39;Option 3&#39;], correct: false },<br>                { &#39;id&#39;: 4, &#39;answerText&#39;: element[&#39;Option 4&#39;], correct: false }<br>              ]</pre><pre>if (question.answers[element[&#39;Answer Index&#39;] - 1] !== undefined) {<br>                question.answers[element[&#39;Answer Index&#39;] - 1].correct = true;<br>              }</pre><pre>// add primary tag to question tag list<br>              question.tags = [this.uploadFormGroup.get(&#39;tagControl&#39;).value];</pre><pre>for (let i = 1; i &lt; 10; i++) {<br>                if (element[&#39;Tag &#39; + i] &amp;&amp; element[&#39;Tag &#39; + i] !== &#39;&#39;) {<br>                  // check for duplicate tags<br>                  if (question.tags.indexOf(element[&#39;Tag &#39; + i].trim()) === -1) {<br>                    question.tags.push(element[&#39;Tag &#39; + i].trim());<br>                  }<br>                }<br>              }</pre><pre>question.published = false;<br>              question.explanation = &#39;status - not approved&#39;;<br>              question.status = QuestionStatus.PENDING;<br>              question.created_uid = this.user.userId;</pre><pre>if (!question.questionText || question.questionText.trim() === &#39;&#39;) {<br>                this.questionValidationError = true;<br>                question.validationErrorMessages.push(&#39;Missing Question&#39;);<br>              } else if (question.answers[0].answerText.trim() === &#39;&#39; || question.answers[1].answerText.trim() === &#39;&#39; ||<br>                question.answers[2].answerText.trim() === &#39;&#39; || question.answers[3].answerText.trim() === &#39;&#39;) {<br>                this.questionValidationError = true;<br>                question.validationErrorMessages.push(&#39;Missing Question Answer Options&#39;);<br>              } else if (question.answers.filter(a =&gt; a.correct).length !== 1) {<br>                this.questionValidationError = true;<br>                question.validationErrorMessages.push(&#39;Must have exactly one correct answer&#39;);<br>              } else if (question.answers.filter(a =&gt; !a.answerText || a.answerText.trim() === &#39;&#39;).length &gt; 0) {<br>                this.questionValidationError = true;<br>                question.validationErrorMessages.push(&#39;Missing Answer&#39;);<br>              } else if (question.tags.length &lt; 3) {<br>                this.questionValidationError = true;<br>                question.validationErrorMessages.push(&#39;Atleast 3 tags required&#39;);<br>              }</pre><pre>return question;<br>            });<br>          this.bulkUploadFileInfo.uploaded = this.questions.length;<br>        }<br>      });<br>  }</pre><pre>private prepareUpload(): any {<br>    const input = new FormData();<br>    input.append(&#39;category&#39;, this.uploadFormGroup.get(&#39;category&#39;).value);<br>    input.append(&#39;tag&#39;, this.uploadFormGroup.get(&#39;tagControl&#39;).value);<br>    input.append(&#39;csvFile&#39;, this.uploadFormGroup.get(&#39;csvFile&#39;).value);<br>    return input;<br>  }</pre><pre>onUploadSubmit() {<br>    // validate<br>    if (!this.uploadFormGroup.valid || this.bulkUploadFileInfo === undefined) {<br>      return;</pre><pre>} else {<br>      const formModel = this.prepareUpload();</pre><pre>const dbQuestions: Array&lt;Question&gt; = [];</pre><pre>for (const question of this.questions) {<br>        this.bulkUploadFileInfo.categoryId = this.uploadFormGroup.get(&#39;category&#39;).value;<br>        this.bulkUploadFileInfo.primaryTag = this.uploadFormGroup.get(&#39;tagControl&#39;).value;<br>        question.categoryIds = [this.uploadFormGroup.get(&#39;category&#39;).value];<br>        question.createdOn = new Date();<br>        dbQuestions.push(question);<br>      }<br>      this.bulkUploadFileInfo.created_uid = this.user.userId;<br>      this.bulkUploadFileInfo.date = new Date().getTime();<br>      this.parsedQuestions = dbQuestions;<br>    }<br>  }</pre><pre>...</pre><h4>Profile settings</h4><p>For uploading the avatar for the user profile, we’ll use the <a href="https://www.npmjs.com/package/ngx-image-cropper">ngrx-image-cropper</a> library. This will allow for cropping and adjusting the aspect ration of the uploaded image.</p><p>As with bulk upload, we will use the firebase storage to store user images on the cloud storage.</p><p>See <a href="https://github.com/anihalaney/rwa-trivia/blob/part-17/src/app/user/components/profile-settings/profile-settings.component.ts">profile-settings.component.ts</a> for complete code.</p><h4>Storage rules</h4><p>We need to adjust our rules so that the bulk uploaded files can be created by an authenticated user but restrict read only to the creator. On the other hand, the profile avatar image can be read by everyone.</p><p>The admins will have permissions to everything. Here are the rules -</p><pre>service firebase.storage {</pre><pre>function isAuthenticated() {<br>    return request.auth != null;<br>  }<br> // Add uid of admin user <br> // This is temp solution for time being till fetching role from user document does not work<br>  function isAdmin() {<br>   return isAuthenticated() &amp;&amp; <br>       (request.auth.uid == &#39;&#39;);<br>  }</pre><pre>function isUserAuthenticated(userId) {<br>    return isAuthenticated() &amp;&amp; request.auth.uid == userId;<br>  }<br>  <br>  match /b/{bucket}/o {<br>    match /templates {<br>      // Anyone can view any template file<br>      match /{allFiles=**} {<br>        allow read;<br>      }<br>    }<br>    match /questions {<br>      // questions files, only authenticated user (for now)<br>      match /{allFiles=**} {<br>        allow read, write: if request.auth != null;<br>      }<br>    }<br>     match /bulk_upload/{userId} {<br>      // bulk upload files, only authenticated user allowed to write<br>      // only user is allowed to write user folder<br>      // request.resource != null, ensure that delete operation is not allowed<br>      // resource == null, ensure that update/overwrite operation on file is not allowed<br>      match /{allFiles=**} {<br>        allow write: if isUserAuthenticated(userId)<br>                      &amp;&amp; request.resource != null<br>                      &amp;&amp; resource == null;<br>        allow read:  if isUserAuthenticated(userId)<br>               || isAdmin();                      <br>      }<br>    }<br>    <br>    match /profile/{userId} {<br>      // user profile, only authenticated user allowed to write<br>      // only user is allowed to write user profile<br>      // request.resource != null, ensure that delete operation is not allowed<br>      // resource == null, ensure that update/overwrite operation on file is not allowed      <br>      match /avatar {<br>        match /{allFiles=**} {<br>          allow write: if isUserAuthenticated(userId)<br>                        &amp;&amp; request.resource != null<br>                        &amp;&amp; resource == null;  <br>          allow read:  if isAuthenticated(); <br>          }<br>      }<br>      match /original {<br>        match /{allFiles=**} {<br>          allow write: if isUserAuthenticated(userId)<br>                        &amp;&amp; request.resource != null<br>                        &amp;&amp; resource == null;           <br>          }<br>      }<br>    }    <br>  }<br>}</pre><p>Note the use of hard-coded admin uids for now. We’ll change this over the next parts to use the admin role instead of the uids.</p><p>Complete source code for this part <a href="https://github.com/anihalaney/rwa-trivia/tree/part-17">here</a></p><p>In this part, we added much needed features of bulk upload and user profile using firebase storage for uploaded files. It’s missing the role assignment and some UI changes that we’ll cover in the next part of the series.</p><h4>Next Up</h4><p>Revisiting ngrx and cleanup UI for bulk upload</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">16: Firebase to firestore</a></li></ul><h4>References</h4><ul><li><a href="https://firebase.google.com/docs/storage/">https://firebase.google.com/docs/storage/</a></li><li><a href="https://firebase.google.com/docs/storage/security/">https://firebase.google.com/docs/storage/security/</a></li><li><a href="https://github.com/angular/angularfire2/blob/master/docs/storage/storage.md">https://github.com/angular/angularfire2/blob/master/docs/storage/storage.md</a></li><li><a href="https://firebase.google.com/docs/functions/gcp-storage-events">https://firebase.google.com/docs/functions/gcp-storage-events</a></li><li><a href="https://www.npmjs.com/package/ngx-image-cropper">https://www.npmjs.com/package/ngx-image-cropper</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d3d2c9f5f27c" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-17-cloud-storage-with-firebase-and-angular-d3d2c9f5f27c">Real World App - Part 17: Cloud storage with Firebase and Angular</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real World App - Part 16: From Firebase to Firestore]]></title>
            <link>https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237?source=rss----5fcb8756dcc3---4</link>
            <guid isPermaLink="false">https://medium.com/p/f6c494e80237</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[angularfire2]]></category>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[firestore]]></category>
            <category><![CDATA[elasticsearch]]></category>
            <dc:creator><![CDATA[Akshay Nihalaney]]></dc:creator>
            <pubDate>Thu, 09 Nov 2017 19:46:12 GMT</pubDate>
            <atom:updated>2018-06-29T02:39:03.309Z</atom:updated>
            <content:encoded><![CDATA[<p>Migrating from Firebase realtime database to Cloud Firestore</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6DrNpNpbrQt1v-NQjh9xow.jpeg" /></figure><p><em>This is Part 16 of our Real World App series. For other parts, see links at the bottom of this post or go to </em><a href="https://blog.realworldfullstack.io/">https://blog.realworldfullstack.io/</a><em>. The app is currently under development and can be viewed at </em><a href="https://bitwiser.io"><em>https://bitwiser.io</em></a><em> </em>and the github repo can be found <a href="https://github.com/anihalaney/rwa-trivia">here</a>.</p><h4>Recap</h4><p>In the <a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">previous post</a>, we started incorporating the Material UI design in our app. In this part of the series, we will migrate our data from Firebase realtime database (<strong><em>rtdb</em></strong>) to firestore.</p><h3>Why Firestore?</h3><h4>Limitations of Firebase realtime database</h4><ul><li>Data organized as single JSON tree - the entire data structure is organized in a single JSON tree as opposed to breaking it down into documents and collections.</li><li>Limited querying capabilities - this is by far the biggest issue I have with <em>rtdb</em>. <em>rtdb</em> querying was so limited, that for pretty much any querying I had to rely on the Elasticsearch engine.</li><li>Scalability - while this was a non-issue so far, scaling beyond a certain point requires sharding data across multiple databases.</li></ul><p><strong>What is firestore?</strong></p><p>Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud Platform.</p><p>As Firestore is part of the firebase platform, it offers seamless integration with other Firebase and Google Cloud Platform products, including Cloud Functions.</p><p>Although, still in beta, firestore solves some of the issues with <em>rtdb</em>. In addition to above, it has better support for batch writes &amp; transactions, automatic query indexing and sharding, offline support for web clients and better security provisions. For complete comparison, check out -</p><p><a href="https://firebase.google.com/docs/database/rtdb-vs-firestore">Choose a Database: Cloud Firestore or Realtime Database | Firebase Realtime Database</a></p><h4>Upgrade Angular, Angular Material, angularfire2 to v5</h4><p>As always, we start by upgrading the project to the latest versions. Upgrading Angular and Angular Material (rc0) to v5, cli to 1.5 and typescript to 2.6.1 had no breaking changes to our project.</p><p>Refer to change logs for the new features and breaking changes -</p><ul><li><a href="https://github.com/angular/angular/blob/master/CHANGELOG.md">https://github.com/angular/angular/blob/master/CHANGELOG.md</a></li><li><a href="https://github.com/angular/material2/blob/master/CHANGELOG.md">https://github.com/angular/material2/blob/master/CHANGELOG.md</a></li></ul><p>However, angularfire2 upgrade did require changes as indicated here - <a href="https://github.com/angular/angularfire2/blob/master/docs/version-5-upgrade.md">https://github.com/angular/angularfire2/blob/master/docs/version-5-upgrade.md</a></p><p>The first change involved using the valuechanges() observable on the AngularFireList -</p><pre>//<a href="https://github.com/anihalaney/rwa-trivia/blob/bb0d070bb3aa7fe54a70e0fcd70b87539c7acab3/src/app/core/services/category.service.ts">category.service.ts</a></pre><pre>getCategories(): Observable&lt;Category[]&gt; {    <br>  return this.db.list(&#39;/categories&#39;)<strong>.valueChanges()</strong>;  <br>}</pre><p>The other change was the way the $key and value gets accessed -</p><pre>//<a href="https://github.com/anihalaney/rwa-trivia/blob/bb0d070bb3aa7fe54a70e0fcd70b87539c7acab3/src/app/core/services/question.service.ts">question.service.ts</a></pre><pre>  getUserQuestions(user: User): Observable&lt;Question[]&gt; {<br>    return this.db.list(&#39;/users/&#39; + user.userId + &#39;/questions&#39;).snapshotChanges()<br>      .mergeMap((qids) =&gt; {<br>        return Observable.forkJoin(<br>          qids.map((qid) =&gt; this.db.object(&#39;/questions/&#39; + qid.payload.val() + &#39;/&#39; + qid.payload.key).snapshotChanges()<br>            .take(1)<br>            .map(action =&gt; {<br>              const $key = action.payload.key;<br>              const data = { $key, ...action.payload.val() };<br>              return data;<br>            })<br>            .map(q =&gt; Question.getViewModelFromDb(q))))<br>    });<br>  }</pre><p>For complete changes check out the <a href="https://github.com/anihalaney/rwa-trivia/commit/bb0d070bb3aa7fe54a70e0fcd70b87539c7acab3">commit</a>. Note: I did not bother to optimize these functions as they would need to be modified again for using firestore anyway. Just wanted to make sure we didn’t break anything in the meantime.</p><h4>Migrating data from rtdb to firestore</h4><p>In firestore, the data is organized in documents and collections. Refer - <a href="https://firebase.google.com/docs/firestore/data-model">https://firebase.google.com/docs/firestore/data-model</a></p><p>The migration gives me an opportunity to reorganize the data model a bit. I’ve moved the tag array under lists (we can add more lists here if needed). I’ve also separated out questions and unpublished_questions into their own top-level collections. Even though both these collections use Question as their underlying document model, their usage on different views, search, indexing and security is distinct enough to keep them in separate collections.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1ubG-NsExlMtqchxFTRWTw.png" /><figcaption>Firestore data model</figcaption></figure><p>I also removed the question and game ids from the user hierarchy as this information was redundant and only helped us with querying as the <em>rtdb</em> querying was very limited. Hopefully, with firestore we wouldn’t need this, even though it can always be added as this data can be derived from the properties in the question and game documents.</p><h4>Migration using cloud functions</h4><p>As Firebase cloud functions gives us access to both - <em>rtdb</em> and firestore, I leveraged this to write migration scripts for migrating data. Since firestore supports batch writes, I’ve used the batch object to commit data. Also note that for questions and games, we’ll batch our data in chunks of 100 documents.</p><p>Here is a sample script for migrating questions -</p><pre>//<a href="https://github.com/anihalaney/rwa-trivia/blob/c4b796a5395deea246cca4830c7f75f832aed85e/functions/firestore-migration.ts">firestore-migration.ts</a></pre><pre>export class FirestoreMigration {<br>  constructor(private admin: any) {}</pre><pre>...</pre><pre>  migrateQuestions(sourceList, destinationCollection) {<br>    return new Promise&lt;number&gt;((resolve, reject) =&gt; {<br>      let admin = this.admin;<br>      let questions: Question[] = [];<br>      let qRef = admin.database().ref(sourceList);<br>      qRef.once(&quot;value&quot;, function(qs) {<br>        qs.forEach(q =&gt; {<br>          //console.log(c.key);<br>          console.log(q.val());<br>          let question: Question = q.val();<br>          question.id = q.key;<br>          questions.push(question);<br>        })<br>    <br>        firestoreBatchWrite(destinationCollection, questions, &quot;id&quot;, 0, admin.firestore()).then(l =&gt; {<br>          resolve(l);<br>        });<br>      });<br>    });<br>  }</pre><pre>}</pre><pre>const BATCH_SIZE = 100;<br>function firestoreBatchWrite(collection: string, dataItems: any[], idField: string,<br>    start: number, firestore: any): Promise&lt;number&gt; {<br>  let arr = dataItems.slice(start, start + BATCH_SIZE);</pre><pre>  if (arr.length === 0) {<br>    return Promise.resolve(dataItems.length);<br>  }</pre><pre>  let batch = firestore.batch();<br>  arr.forEach (item =&gt; {<br>    let doc = firestore.doc(collection + &quot;/&quot; + item[idField]);<br>    console.log(doc);<br>    batch.set(doc, item);<br>  });<br>  console.log(&quot;Commiting questions batch: &quot; + start);<br>  return batch.commit().then(() =&gt; {<br>    return firestoreBatchWrite(collection, dataItems, idField, start + BATCH_SIZE, firestore);<br>  });</pre><pre>}</pre><p>Refer to the firestore documentation for their api -</p><ul><li><a href="https://firebase.google.com/docs/reference/js/firebase.firestore">https://firebase.google.com/docs/reference/js/firebase.firestore</a></li><li><a href="https://firebase.google.com/docs/reference/functions/functions.firestore">https://firebase.google.com/docs/reference/functions/functions.firestore</a></li></ul><h4>Firestore security rules</h4><p>The firestore security rules control authorization for direct access to from mobile and web clients to firestore data. (Note these rules do not impact server-side or cloud function access).</p><p>Firestore security rules have similarities to <em>rtdb</em> security rules, even though they are syntactically different and have subtle differences like rules do not automatically cascade, unless widlcards are used. For a complete guide refer to - <a href="https://firebase.google.com/docs/firestore/security/get-started">https://firebase.google.com/docs/firestore/security/get-started</a></p><pre>service cloud.firestore {<br>  match /databases/{database}/documents {</pre><pre>match /users/{userId} {<br>          allow read: if request.auth != null &amp;&amp; request.auth.uid == userId;<br>    }<br>    <br>  match /{document=**} {<br>      allow read, write: if request.auth != null &amp;&amp; get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.admin;<br>    }<br>    <br>    match /categories {<br>      match /{document=**} {<br>        allow read;<br>      }<br>    }</pre><pre>match /lists {<br>      match /{document=**} {<br>        allow read;<br>      }<br>    }<br>    <br>    match /questions {<br>      match /{document=**} {<br>        allow read: if request.auth != null &amp;&amp; request.auth.uid == resource.data.created_uid;<br>      }<br>    }<br>    <br>    match /unpublished_questions {<br>      match /{document=**} {<br>        allow read, update: if request.auth != null &amp;&amp; request.auth.uid == resource.data.created_uid;<br>        allow create: if request.auth != null &amp;&amp; request.auth.uid == request.resource.data.created_uid;<br>      }<br>    }<br>    <br>    match /games {<br>      match /{document=**} {<br>        allow read, update: if request.auth != null &amp;&amp; (request.auth.uid == resource.data.playerId_0 || request.auth.uid == resource.data.playerId_1);<br>        allow create: if request.auth != null &amp;&amp; request.auth.uid == request.resource.data.playerId_0;<br>      }<br>    }<br>    <br>  }<br>}</pre><p>The rules are pretty self-explanatory once you go thru the docs. The following notes explain the rules above -</p><ul><li>To determine if the user has admin role, we need to go thru the document path /databases/$(database)/documents/users/$(request.auth.uid) and get the roles. We must first grant access to this path for the data to be evaluated. The first section “match /users/{userId} { …” allows read access to users collection and the userId’s path for the authenticated user.</li><li>admin role can read/write all data</li><li>published questions can be read only by the creator. They cannot be modified</li><li>unpublished questions can be read/updated by the creator. The creators user id must match created_uid</li><li>games can be read only by one of the players, while it needs to be created by the first player for the game</li></ul><h4>Modify Angular code to replace rtdb with firestore</h4><p>We will replace the AngularFireDatabaseModule with AngularFirestoreModule. The <a href="https://github.com/anihalaney/rwa-trivia/blob/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/src/app/core/services/category.service.ts">category</a> and <a href="https://github.com/anihalaney/rwa-trivia/blob/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/src/app/core/services/tag.service.ts">tag </a>services are pretty simple to convert.</p><p>The question and game service are interesting. Let’s cover the changes in those -</p><pre>//<a href="https://github.com/anihalaney/rwa-trivia/blob/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/src/app/core/services/question.service.ts">question.service.ts</a></pre><pre>...</pre><pre>  getUserQuestions(user: User, published: boolean): Observable&lt;Question[]&gt; {<br>    let collection = (published) ? &quot;questions&quot; : &quot;unpublished_questions&quot;;<br>    return this.db.collection(`/${collection}`, ref =&gt; ref.where(&#39;created_uid&#39;, &#39;==&#39;, user.userId))<br>        .valueChanges()<br>        .map(qs =&gt; qs.map(q =&gt; Question.getViewModelFromDb(q)));<br>  }</pre><pre>  saveQuestion(question: Question) {<br>    let dbQuestion = Object.assign({}, question); //object to be saved</pre><pre>    let questionId = this.db.createId();<br>    dbQuestion.id = questionId;</pre><pre>    //Use the set method of the doc instead of the add method on the collection, so the id field of the data matches the id of the document<br>    this.db.doc(&#39;/unpublished_questions/&#39; + questionId).set(dbQuestion).then(ref =&gt; {<br>        this.store.dispatch(this.questionActions.addQuestionSuccess());<br>    });<br>  }</pre><pre>  approveQuestion(question: Question) {<br>    let dbQuestion = Object.assign({}, question); //object to be saved</pre><pre>    let questionId = dbQuestion.id;<br>    dbQuestion.status = QuestionStatus.APPROVED;</pre><pre>//Transaction to remove from unpublished and add to published questions collection    <br>    this.db.firestore.runTransaction(transaction =&gt; {<br>      return transaction.get(this.db.doc(&#39;/unpublished_questions/&#39; + questionId).ref).then(doc =&gt;<br>       transaction.set(this.db.doc(&#39;/questions/&#39; + questionId).ref, dbQuestion).delete(doc.ref)<br>      );<br>    })<br>  }<br>...</pre><p>The getUserQuestions above uses the where clause [<em>where(‘created_uid’, ‘==’, user.userId))</em>] to get the questions for the user.</p><p>The saveQuestion uses “this.db.createId()” to first get the id for the question. It then uses this as the id of the document to be created. Note the use of Object.assign to convert the instance of a Typescript class to a pure object that can be persisted in the database.</p><p>The approveQuestion makes uses of a transaction to create a new document in questions collection and delete it from the unpublished_questions collection.</p><p>The game service makes use of the update method as below -</p><pre>//<a href="https://github.com/anihalaney/rwa-trivia/blob/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/src/app/core/services/game.service.ts">game.service.ts</a></pre><pre>...</pre><pre>  addPlayerQnAToGame(game: Game, playerQnA: PlayerQnA) {<br>    game.playerQnAs.push(playerQnA);<br>    let dbGame = game.getDbModel();<br>    <br>    this.db.doc(&#39;/games/&#39; + game.gameId).update(dbGame);<br>  }<br>...</pre><pre>}</pre><pre>...</pre><p>The rest of the service method uses code similar to one of the above scenarios. For code changes to all the services, check out the github repo <a href="https://github.com/anihalaney/rwa-trivia/tree/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/src/app/core/services">commit</a>.</p><h4>onWrite Cloud functions event</h4><p>The onWrite cloud functions event needs to listen to the firestore instead of the <em>rtdb </em>database -</p><pre>//<a href="https://github.com/anihalaney/rwa-trivia/blob/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/functions/db.ts">db.ts</a></pre><pre>...</pre><pre>exports.onQuestionWrite = functions.firestore.document(&#39;/questions/{questionId}&#39;).onWrite(event =&gt; {<br>    const data = event.data.data();<br>    if (data) {<br>      //add or update<br>      ESUtils.createOrUpdateIndex(ESUtils.QUESTIONS_INDEX, data.categoryIds[&quot;0&quot;], data, event.params.questionId);<br>    }<br>    else {<br>      //delete<br>      ESUtils.removeIndex(ESUtils.QUESTIONS_INDEX, event.params.questionId);<br>    }<br>});</pre><p>And finally, we would change the reindex method in our cloud functions <a href="https://github.com/anihalaney/rwa-trivia/blob/3b001f3c9f72aabbab5f3a6fe66da716b433ae60/functions/app.ts">app.ts</a> to use the firestore’s questions collection.</p><p>Complete source code for this part <a href="https://github.com/anihalaney/rwa-trivia/tree/part-16">here</a></p><p>In this part, we migrated our data from Firebase realtime database to Cloud Firestore. I chose to do this sooner rather than later as we’re gonna keep expanding our reliance on the database for it’s rich features and delaying it would have meant only more work to be done later.</p><h4>Next Up</h4><p>I keep pushing the development of the core functionality of bulk question upload, dashboard features and complete game play mechanics. Next few parts will focus on building out these functional core of the app.</p><p><em>If you enjoyed this article please recommend and share and feel free to provide your feedback on the comments section.</em></p><h4>Real World App Series (quick links) :</h4><p>Series summary and index: <a href="https://blog.realworldfullstack.io/real-world-angular-part-x-fantastic-4-c714b04640ab">Part X</a></p><ul><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-0-from-zero-to-cli-ng-a2ff646b90cc">0: From zero to cli-ng</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-1-not-another-todo-list-c2ea5020f944">1: Not another todo list!</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-2-its-a-material-world-2d70238ef8ef">2: It’s a Material World!</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-3-form-formation-f78d8462da70">3: Form Formation</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-4-state-of-my-spa-10bf90c5a15">4: State of my SPA</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-5-light-my-fire-34b0bcb351a8">5: Light my fire</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-6-3rs-rules-roles-routes-9e7de5a3ea8e">6: 3Rs — Roles, Rules &amp; Routes</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-6-1-upgrading-to-4-0-0-rc-2-fcaab81603fa">6.1: Upgrading to 4.0.0-rc.2</a></li><li><a href="https://blog.realworldfullstack.io/real-world-angular-part-7-lazy-coding-load-splitting-4552f5f54ef7">7: Split my lazy loaded code</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-8-just-ahead-of-in-time-ae2d3cc89656">8: Just Ahead of in Time</a></li><li>Parts <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-unit-testing-c62ba20b1d93">9</a>, <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-1-more-unit-testing-f0545ece586d">9.1</a> and <a href="https://blog.realworldfullstack.io/real-world-angular-part-9-2-even-more-unit-tests-f903df40530a">9.2</a> : Unit Testing</li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-11-gameplay-with-angular-2a660fad52c2">11: Gameplay with Angular</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-12-cloud-functions-for-firebase-8359787e26f3">12: Cloud Functions for Firebase</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-13-elasticsearch-on-google-cloud-with-firebase-functions-8a24fa2b95ed">13: Elasticsearch on google cloud</a>; <a href="https://blog.realworldfullstack.io/real-world-app-part-14-faceted-search-with-elasticsearch-and-angular-material-data-table-d90ebaf2ee4b">14: Faceted Search</a></li><li><a href="https://blog.realworldfullstack.io/real-world-app-part-15-ui-design-with-angular-material-1a5c597c679e">15: Material UI Design</a></li></ul><h4>References</h4><p><em>Changelogs</em></p><ul><li><a href="https://github.com/angular/angular/blob/master/CHANGELOG.md">https://github.com/angular/angular/blob/master/CHANGELOG.md</a></li><li><a href="https://github.com/angular/material2/blob/master/CHANGELOG.md">https://github.com/angular/material2/blob/master/CHANGELOG.md</a></li><li><a href="https://github.com/angular/angularfire2/blob/master/docs/version-5-upgrade.md">https://github.com/angular/angularfire2/blob/master/docs/version-5-upgrade.md</a></li></ul><p><em>Firestore Overview</em></p><ul><li><a href="https://firebase.google.com/docs/firestore/rtdb-vs-firestore">https://firebase.google.com/docs/firestore/rtdb-vs-firestore</a></li><li><a href="https://firebase.google.com/docs/firestore/firestore-for-rtdb">https://firebase.google.com/docs/firestore/firestore-for-rtdb</a></li><li><a href="https://firebase.google.com/docs/firestore/data-model">https://firebase.google.com/docs/firestore/data-model</a></li><li><a href="https://firebase.google.com/docs/firestore/security/get-started">https://firebase.google.com/docs/firestore/security/get-started</a></li></ul><p><em>Firestore API Reference</em></p><ul><li><a href="https://firebase.google.com/docs/reference/js/firebase.firestore">https://firebase.google.com/docs/reference/js/firebase.firestore</a></li><li><a href="https://firebase.google.com/docs/reference/functions/functions.firestore">https://firebase.google.com/docs/reference/functions/functions.firestore</a></li></ul><p><em>Other Posts</em></p><ul><li><a href="https://blog.cloudboost.io/angular-5-with-firebases-cloud-firestore-87b5d6c13ff">https://blog.cloudboost.io/angular-5-with-firebases-cloud-firestore-87b5d6c13ff</a></li><li><a href="https://blog.angular.io/improved-querying-and-offline-data-with-angularfirestore-dab8e20a4a06">https://blog.angular.io/improved-querying-and-offline-data-with-angularfirestore-dab8e20a4a06</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f6c494e80237" width="1" height="1" alt=""><hr><p><a href="https://blog.realworldfullstack.io/real-world-app-part-16-from-firebase-to-firestore-f6c494e80237">Real World App - Part 16: From Firebase to Firestore</a> was originally published in <a href="https://blog.realworldfullstack.io">Real World Full Stack</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>