2016年12月30日 星期五

[ASP.NET Core X Angular] (11) - BlockUI by ComponentFactoryResolver

 Angular     ComponentFactoryResolver    BlockUI   Font-awesome 


Introduction


Since Angular2 doesn’t have
$compile equivalent, we could use ComponentFactoryResolver class to create the component factory and dynamically create a component.

We will make a sample, blockUI, which is a component that could be created in another component and destroyed at run-time.

Related articles


Environment

Angular 2.1.0


Implement


HTML and CSS

First, we should have the blockUI’s HTML and CSS.


blockUI.component.ts

import {Component, Input} from '@angular/core';

@Component({
    selector: 'block-ui',
    template:
    `<div class="in modal-backdrop blockui-overlay"></div>
    <div class="blockui-message-container">
    <div class="blockui-message" [ngClass]="blockuiMessageClass">
      {{state.message}} <i class="fa fa-cog fa-spin"></i>
    </div>
    </div>`,
    styleUrls: ['/app/component/blockUI/blockUI.component.css']
})


export class BlockUIComponent {
    @Input() private blockuiMessageClass;
    private state: any;

    constructor() {
        this.state = {message: 'Loading...'};
    }
}

blockUI.component.css

.blockui-overlay {
  background-color: white;
  cursor: wait;
}

.blockui-message-container {
  position: absolute;
  top: 35%;
  left: 0;
  right: 0;
  height: 0;
  text-align: center;
  z-index: 10001;
  cursor: wait;
}

.blockui-message {
  display: inline-block;
  text-align: left;
  background-color: #333;
  color: #f5f5f5;
  padding: 20px;
  border-radius: 4px;
  font-size: 20px;
  font-weight: bold;
  filter: alpha(opacity=100);
}

.modal-backdrop.in {
    filter: alpha(opacity=50);
    opacity: .5;
}

.modal-backdrop {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1040;
    background-color: #000;
}



ComponentFactoryResolver

Let’s create a service for blockUI.

Notice that after Angular 2.2, there are some updates on ApplicationRef, and we could not use appRef['_rootComponents'][0]['_hostElement'].vcRef to get root component’s view anymore.


blockUI.service.ts (with Angular 2.1.0 or earlier version)

import {Injectable, ApplicationRef, ComponentRef, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import {BlockUIComponent} from './blockUI.component';

@Injectable()
export class BlockUIService {
    private blockUI: ComponentRef<BlockUIComponent>;

    constructor(
        private appRef: ApplicationRef,
        private componentFactoryResolver: ComponentFactoryResolver) { }

    public start() {

        let viewContainerRef: ViewContainerRef = this.appRef['_rootComponents'][0]['_hostElement'].vcRef;
        let factory = this.componentFactoryResolver.resolveComponentFactory(BlockUIComponent);
        this.blockUI = viewContainerRef.createComponent(factory);
    }


    public stop() {
        if (this.blockUI) {
            this.blockUI.destroy();
        }
    }
}



blockUI.service.ts (with latest Angular, 2.4.0 is tested fine)

In this version, we have to set the root component view from outside.
So we declare a PUBLIC ViewContainerRef variable.

import {Injectable, ApplicationRef, ElementRef, ComponentRef, ViewContainerRef, ComponentFactory,ComponentFactoryResolver } from '@angular/core';
import {BlockUIComponent} from './blockUI.component';

@Injectable()
export class BlockUIService {
    private blockUI: ComponentRef<BlockUIComponent>;
    public vRef: ViewContainerRef;

    constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

    public start() {
        let factory = this.componentFactoryResolver.resolveComponentFactory(BlockUIComponent);
        this.blockUI = this.vRef.createComponent(factory);
    }


    public stop() {
        if (this.blockUI) {
            this.blockUI.destroy();
        }
    }
}






A reference to an Angular application running on a page. We can use this class to get the root component. For example,




 

An instance of a Component created via a ComponentFactory.
In this sample, we need create a dynamic ComponentRef<BlockUIComponent> instance.


The View Container could be attached with one or more View.
We can insert the blockUI View to root component by function: createComponent

viewContainerRef.createComponent(factory);

and destroy the blockUI view by function: remove.



Use ComponentFactoryResolver class to create the component factory and the blockUI component.


Usage

Here is an example for using our blockUI.

App.module.ts

import { BlockUIComponent } from './components/blockUI/blockUI.component';
import { BlockUIService } from './components/blockUI/blockUI.service';

@NgModule({
    declarations: [
      //...
      BlockUIComponent
    ],
    imports: [
      //...
    ],
    entryComponents: [
          BlockUIComponent
    ],
    providers: [
        //...
        BlockUIService
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }


What is entryComponents?

It’s used for compiling the dynamic component with ViewContainerRef.createComponent(). Notice that the components registered in routes are also automatically added to entryComponents as well.



Any component


//For the old version of BlockUIService with Angular 2.1.0 or earlier
//constructor(
//  private blockUI: BlockUIService) {
//}

constructor(
    private viewContainerRef: ViewContainerRef,
    private blockUI: BlockUIService) {
        this.blockUI.vRef= this.viewContainerRef;
}

private getLaggers() {
    this.blockUI.start();
    return new Promise(
      resolve => {
          this.ypService.getLaggers().then(
            data => {
                this.laggers = data;
                resolve();
                this.blockUI.stop();
            }, err => { console.log(err); });

      });
}





Demo






Github









Reference










2016年12月29日 星期四

[ASP.NET Core X Angular] (10) - Firebase integration

 Angular     MVVC    ASP.NET Core    Firebase    angularfire2  


Introduction


We will integrate Firebase database service in this article with angularfire2 package.

Related articles


Environment

Visual Studio 2015 Update 3
NPM: 3.10.3                                    
Microsoft.AspNetCore.Mvc 1.0.0
angular 2: 2.1.0
angularfire2 : 2.0.0-beta.6



Firebase


Firebase* is a NoSql cloud databse, create a new project and enable read and write permission before we start implementing authorization.

*For more information, go to https://firebase.google.com/docs/database/

Update the rules like this, see more database rules here.

{
  "rules": {
    ".read": true,
    ".write": true
  }
}




Install angularfire2

Use the following command to install angularfire2 and firebase*

$>npm install angularfire2 –save
$>npm install firebase –save

PS. Since angularfire2 is in beta, it doesn’t support “Storage” api so far. However, we can still use firebase official js library to develop the functions.
 
Optional: If you develop with Angular CLI and want to deploy the website to firebase, in stall firebase-tools as well.

Then you should update systemjs.config.js to include the packages.











Integrate Firebase database


We will update the sample codes in [ASP.NET X Angular2](7) – Sub-routing, which contains the constant products’ data in memory not in database.


Our goal is making the product.service support CRUD functions for data in Firebase.


Firebase configuration

Create a FirebaseConfig module to store the api key and other necessary information for connecting to firebase.

PS. You can find your api key in Firebase by clicking the icon below.



FirebaseConfig.ts

export module FirebaseConfig {
    var config: any;
    export function Get() {
        config = {
            apiKey: "XXXXXXXXXXXX",
            authDomain: "xxxx.firebaseapp.com",
            databaseURL: "https://xxxx.firebaseio.com",
            storageBucket: "xxxxx.appspot.com",
            messagingSenderId: "XXXXXXXXXXX"
        };

        return config;
    }
}


product.app.module.ts

Inject the angularfire2 package and api key config in to product.app.module.ts

//...
import { AngularFireModule } from 'angularfire2';
import {FirebaseConfig} from '../../class/FirebaseConfig';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        ProductRoutes,
        AngularFireModule.initializeApp(FirebaseConfig.Get())
    ],
    declarations: [
        //...
    ],
    bootstrap: [ProductAppComponent]
})
export class ProductAppModule { }




Update product.service with Firebase integration

Now we are going to update product.service to get the data from Firebase.

Tips:
Notice that everything in Firebase is a URL, so if the database structure is like this,




Get the entire products:
constructor(private af: AngularFire) {}
this.af.database.object('/Demo/products');


Get the first object with key:
this.af.database.object('/Demo/products/0');


Okay, let’s update our Service to integrate Firebase real-time data.

product.service.ts

//...
import { AngularFire, FirebaseListObservable  } from 'angularfire2';

@Injectable()
export class ProductService {
    constructor(private af: AngularFire) {}

    //Create
    public create(prod:Product){
        return new Promise(
            resolve => {
                let itemObservable = this.af.database.object('/Demo/products/' + prod.Id);
                itemObservable.set(prod); //Set will overwrite
                resolve();
            });
    }

    //Update
    public update(prod: Product) {
        return new Promise<Product>(
            resolve => {
            let itemObservable = this.af.database.object('/Demo/products/' + prod.Id);
            itemObservable.update(prod); //Update current data
            resolve();
        })

    };

    //Remove
    public remove(prod: Product) {
        return new Promise(
            resolve => {
            let itemObservable = this.af.database.object('/Demo/products/' + prod.Id);
            itemObservable.remove(); //Remove current data
            resolve();
        })
    };


    //Get data with key
    public get(key: string) {
        return new Promise<Product>(
            resolve => {
            this.af.database.object('/Demo/products/' + key).subscribe(data => {
                resolve(data);
            })
        });
    }
   

    //Get books (or toys/music)
    public getBooks() {
        return new Promise<Product[]>(
            resolve => {

            this._queryProducts().subscribe(data => {
                if (data) {
                    let books = data.filter(x => x.Type == "Book");
                    resolve(books);
                }
                else {
                    resolve([]);
                }

            })

        });
    }
   
    //Query data from firebase
    private _queryProducts() {
        return this.af.database.object('/Demo/products');
    }
}

Notice that we could use “retrieving-data-as-objects” way in the above codes, or either use “retrieving-data-as-lists” way to complete the same functions.



Demo





Integrate Firebase authentication


Remember that in the beginning we set both “read” and “write” are PUBLIC with the database.

Now try set the rules as “must-be-authorized”,

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

And you will get the error message like this…
EXCEPTION: permission_denied at /Demo/products: Client doesn't have permission to access the desired data.

So in the next step, we have to create a login and authorization page for Firebase and make sure the authorized user could have the permission to read/write the database.


Enable OAuth provider

We will use Google OAuth for example, make sure the Google Authentication provider  is enabled in your Firebase console.




Enable OAuth provider

We will put the login/logout functions on the below app.component.
If the user already logins, show the user’s information and logout button on the page.



app.component.html

<div class="card">
    <div class="card-block">
        <div class="card-text" [ngSwitch]="isAuth">
            <div *ngSwitchCase="false" class="text-center">
                <button class="btn btn-toolbar" (click)="login('google')">
                                <img width="30" src="../asset/images/logo/google-logo.png" />
                                Use Google Account
                            </button>
            </div>
            <div *ngSwitchCase="true" class="text-center">
                <table class="table">
                    <tr>
                        <td class="text-center">
                            <label class="control-label">{{user.name}}</label>
                        </td>
                    </tr>
                    <tr>
                        <td class="text-center">
                            <label class="control-label">{{user.email || '(no email)'}}</label>
                        </td>
                    </tr>
                </table>
                <div>
                    <input type="button" class="btn btn-warning" (click)="logout()" value="Logout" />
                </div>
            </div>
        </div>

    </div>
</div>



The HTML should be displayed like this.










app.component.ts

Here are the login/logout functions, notice that we check the user’s login state in constructor.


/// <reference path="../lib-npm/typings/jsnlog.d.ts" />
import { Component, OnInit } from '@angular/core';
import { AngularFire, AuthProviders, AuthMethods } from 'angularfire2';


@Component({
    selector: 'core-app',
    templateUrl:'/app/app.component.html'
})
export class AppComponent implements OnInit {

    private isAuth = false;
    private user = {};


    constructor(private af: AngularFire) {

        //Check the login state
        this.af.auth.subscribe(
            user => this._changeState(user),
            error => JL("Angular2").error(error)
        );
    }

    ngOnInit() {

    }

    //Login
    private login(provider: string) {
        this.af.auth.login({
            provider: this._getProvider(provider),
            method: AuthMethods.Popup
        });
    }

    //Logout
    private logout() {
        this.af.auth.logout();
    }

    //Check if the user is login or not
    private _changeState(user: any = null) {
        if (user) {
            this.isAuth = true;
            this.user = this._getUserInfo(user)
        }
        else {
            this.isAuth = false;
            this.user = {};
        }
    }

    //Get the user information from Provider's user data
    private _getUserInfo(user: any): any {
        if (!user) {
            return {};
        }
        let data = user.auth.providerData[0];
        return {
            name: data.displayName,
            avatar: data.photoURL,
            email: data.email,
            provider: data.providerId
        };
    }

    private _getProvider(provider: string) {
        switch (provider) {
            case 'twitter': return AuthProviders.Twitter;
            case 'facebook': return AuthProviders.Facebook;
            case 'github': return AuthProviders.Github;
            case 'google': return AuthProviders.Google;
        }
    }


app.module.ts

Of course we have to import the necessary AngularFireModule, AuthProviders, AuthMethods and also initialize AngularFireModule in app.module.ts

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }  from './app.component';
import { AngularFireModule, AuthProviders, AuthMethods } from 'angularfire2';
import {FirebaseConfig} from './class/FirebaseConfig';

@NgModule({
    imports: [
        BrowserModule,
        AngularFireModule.initializeApp(FirebaseConfig.Get())
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }





Demo






Github






Reference