2016年10月23日 星期日

[ASP.NET Core X Angular2](3) – Sub component (input & emit)

 Angular2     MVC core    ASP.NET Core




Introduction


We will try creating the sub component and connect the information between it and its parent component.


Related articles

Environment

Visual Studio 2015 Update 3
NPM: 3.10.3                                    
Microsoft.AspNetCore.Mvc 1.0.0
angular 2: 2.1.0


Create Sub-component


Extending the previous example, we want to show the customer’s details inside the customer.index.component.html

So here what we are going to do
1.  Create Customer class for strong typed programming
2.  Create detail component
3.  Send the selected customer to detail component
4.  Hide the list of customers and display the selected customer’s detail


Customer class

We will create Customer class for strong typed data.


/app/class/Customer.ts

export class Customer {
    Id: number;
    Name: string;
    Phone: string;
    Age: number;
}

Because the class is not under the same path of customer components, we have to load it thru Systemjs. Open /app/systemjs.config.js and add the new settings for all the class within /app/class

systemjs.config.js

System.config({
    transpiler: 'typescript',
    typescriptOptions: { emitDecoratorMetadata: true },
    map: {
        //Skip…
'class': 'app/class'
    },
    paths: {
        //Skip…
        "class:*": "app/class/*.js"
    },
    packages: {
        //Skip…
        'class': { main: '*.js', defaultExtension: 'js' }
    }
});



Create Customer detail component

We are going to make a child component: Customer detail, for main component: Cusomter index.
When clicking on the customer’s name, hide the customers’ list and display the content of child component.

/app/Basic/Customer/customer-detail.component.ts

import {Component, OnInit, Input} from '@angular/core';
import {Customer} from '../../class/Customer';
@Component({
    selector: 'customer-detail',
    templateUrl: '/app/Basic/Customer/customer-detail.component.html',
    styleUrls: ['/app/Basic/Customer/customer-detail.component.css']
})
export class CustomerDetailComponent implements OnInit {

    @Input('selectedCustomer') customer: Customer;
    constructor() {
    }

    ngOnInit() {
    }
}

Notice that we have an INPUT VARIABLE: customer, alias name: selectedCustomer, in the child directive.

Then import it to customer.app.module.

/app/Basic/Customer/customer.app.module.ts

//Skip...
import {CustomerDetailComponent} from './customer-detail.component';
@NgModule({
    //Skip...
    declarations: [CustomerAppComponent, CustomerIndexComponent, CustomerCreateComponent, CustomerEditComponent, CustomerDetailComponent],
})
//...




Update Customer Index Component


/app/Basic/Customer/customer.index.component.html

<div [hidden]="selectedCustomer">   
    <div>
        <input type="button" class="btn btn-primary" value="Create New" (click)="goToCreate()" />
    </div>
    <br />
    <div>
        <table class="table table-bordered table-hover">
            <thead><tr>
                    <td>ID</td>
                    <td class="text-center">Name</td>
                    <td class="text-center">Phone</td>
                    <td class="text-center">Age</td>
                    <td></td>
                </tr></thead>
            <tbody>
                <tr *ngFor="let item of customers; let sn=index">
                        <td class="col-sm-1 text-center">{{item.Id}}</td>
                    <td class="col-sm-2">
                        <a [innerHtml]="item.Name" (click)="showDetail(item)"></a>
                    </td>
                    <td class="col-sm-2">{{item.Phone}}</td>
                    <td class="col-sm-1">{{item.Age}}</td>
                    <td class="col-sm-2"><input type="button" class="btn btn-info" value="Edit" (click)="editCustomer(item)" />&nbsp;<input type="button" class="btn btn-danger" value="Delete" (click)="deleteCustomer(item)" /></td>
                </tr></tbody></table>
    </div>
</div>

<div *ngIf="selectedCustomer">
    <customer-detail [selectedCustomer]="selectedCustomer" >
    </customer-detail>
    <input type="button" class="btn btn-primary" value="Back" (click)="backToList()" />
</div>

Because we have an input variable in Detail component, we need to set a Property binding: [selectedCustomer]= "selectedCustomer" for it.
PS. The latter selectedCustomer is the object in parent component.


/app/Basic/Customer/customer.index.component.ts

Also we add the events for the parent component.

export class CustomerIndexComponent implements OnInit {
    title: string;
    customers: Customer[];
    selectedCustomer: Customer;
    constructor(private router:Router) {
        this.title = "Customers:Index";
        this.customers = CUSTOMERS;
    }

    //Go to create page
    private goToCreate() {
        this.router.navigate(['Basic/Customer/Create']);
    }
    //Back to list (Show list)
    private backToList() {
        this.selectedCustomer = null;
    }
    //Show details of the customer
    private showDetail(cust: Customer) {
        this.selectedCustomer = cust;
    }

   const CUSTOMERS: Customer[] = …
}




Result









Emit event from Sub-component


We can send something from sub-component to parent with EventEmitter.
We will
1.  Create a message class: SysEvent
2.  Emit an object as SysEvent, from sub-component to parent component. That means form customer-detail.component to customer-index.component.
3.  Parent component will show the received message.

SysEvent class

/app/class/SysEvent.ts

export class SysEvent {
    Title: string;
    Msg: string;
    CreateBy: string;
    CreateOn: Date = new Date();

    constructor(fields?: {
        Title?: string,
        Msg?: string,
        CreateBy?: string,
        CreateOn?: Date;
}) {
        if (fields) Object.assign(this, fields);
}
}

In the class’s constructor, use Object.assign to create the initial properties values


EventEmitter

Create an EventEmitter in Customer Detail component and the call-back function in Customer Index component.

/app/Basic/Customer/customer-detail.component.ts

import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
import {SysEvent} from '../../class/SysEvent';

@Component({
    selector: 'customer-detail',
    templateUrl: '/app/Basic/Customer/customer-detail.component.html'
})
export class CustomerDetailComponent implements OnInit {

    @Input('selectedCustomer') customer: Customer;
    @Output('emit-events') emitEvents = new EventEmitter<SysEvent[]>(true); //Must set the EventEmitter to async

    ngOnInit() {
        //Emit event
let evts: SysEvent[] = [
           new SysEvent({
             Title: "Info",
             Msg: "You are looking at " + this.customer.Name + "'s information.",
             CreateBy: "angular",
             CreateOn: new Date()
            })
];
this.emitEvents.emit(evts);
    }
}

Notice that we have to initialize the emitEvents in async mode.




If not, we will get the following message which angular is saying that it cannot get the correct value from the child component.

core.umd.min.js:28 EXCEPTION: Error in /app/Basic/Customer/customer-index.component.html:48:5 caused by: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'.  



/app/Basic/Customer/customer-index.component.ts

Set a call-back for the EventEmitter.

events:SysEvent[]

private setSysEvents(data: SysEvent[]) {
    this.events = data;
}


/app/Basic/Customer/customer-index.component.html

<!-- Skip  ... -->

<div *ngIf="selectedCustomer">
    <customer-detail [selectedCustomer]="selectedCustomer" (emit-events)="setSysEvents($event)">
    </customer-detail>
    <input type="button" class="btn btn-primary" value="Back" (click)="backToList()" />
</div>

<!-- New html  -->

<hr />
<div *ngIf="events">
    <div class="list-group" *ngFor="let evn of events; let sn=index">
        <a href="#" class="disabled list-group-item" [innerHtml]="evn.Msg"></a>
    </div>
</div>



Result

























What’s next?

In the next tutorial, we will use angular’s pipes and our custom pipe to provide a more clean message from the Customer Detail Component.


Github






Reference

Angular 2 document



沒有留言:

張貼留言