###### tags: `Angular` `Params`
# 🌝[T]Passing Parameters 參數傳遞
## 單頁參數傳遞

### #Input
**子層(Child)**
`@Input()` 輸入屬性,通常為接收數據資料,也就是就是讓父層(Parent)將資料傳送到子層(Child)中使用。
==child.ts==
```typescript
@Input() private setting: Setting;
```
**父層(Parent)**
若父層想使用子層的`@Input()`,就必須在HTML使用**中括號**包住欲使用的`@Input()`,這樣就能透過`@Input()`將父層(Parent)的值傳給子層(Child)。
==parent.html==
```HTML
<app-a1-grid [setting]=setting></app-a1-grid>
```
### #Output
**子層(Child)**
`@Output()` 輸出屬性,通常提供事件給外部呼叫回傳使用,也就是讓Child將資料傳回Parent中使用。
==child.ts==
```typescript
@Output() private onGridGoDetail: EventEmitter<any> = new EventEmitter<any>();
private methodFromParentUpdata(params) {
this.onGridGoDetail.emit(params);
}
```
**父層(Parent)**
若父層想使用子層的`@Output()`,就必須在HTML使用**小括號**包住欲使用的`@Output()`,這樣就能在子層(Child)觸發事件時,父層(Parent)收到子層(Child)的值。
==parent.html==
```HTML
<app-a1-grid (onGridGoDetail)="methodFromParentUpdata_($event)"></app-a1-grid>
```
==parent.ts==
```typescript
methodFromParentUpdata_(event){
// event為子層(Child)傳過來的值
}
```
### #ViewChild
可以直接引用子元件的所有public事件、屬性。
==child.ts==
```typescript
...
export class A1GridComponent{}
...
```
==parent.html==
```HTML
<app-a1-grid></app-a1-grid>
```
==parent.ts==
```typescript
@ViewChild(A1GridComponent) a1Grid: A1GridComponent;
...
this.a1Grid.setGridRowData(this.gridData);
```
[[Angular] ViewChild及ContentChild介紹](https://jiaming0708.github.io/2017/02/23/angular-viewchild-contentchild/)
### #Router
==傳遞端== 設定State,`state`須為物件,有兩種方式可以設置`state`。
#### #1 使用router.navigateByUrl(),傳遞state物件。
.ts
```typescript
constructor(public router: Router) {}
this.router.navigateByUrl('/eOrder/eShopping/ec/orders', { state: { iEC: 0, iStoreID: this.store.ID } });
```
#### #2 使用routerLink指令聲明Input,傳遞state物件。
.html
```htmlmixed
<a routerLink="/eOrder/eShopping/ec/orders" [state]="{ iEC: 3 }">
<a routerLink="/eOrder/eShopping/ec/orders" [state]="{ iEC: 4 }">
```
==接收端== 讀取State,有兩種方式可以取得由Router傳遞的`state`。
#### #1 路由轉跳完成後,使用window.history.state取得。
.ts
```typescript
constructor(private route: ActivatedRoute) {}
/** 取得傳入的No */
getRouteParamMap() {
return this.route.paramMap.pipe(map(() => window.history.state));
}
```
#### #2 從NavigationStart事件的NavigationExtras取得。
.ts
```typescript
constructor(public router: Router) { }
this.state$ = this.router.events.pipe(
filter(e => e instanceof NavigationStart),
map(() => this.router.getCurrentNavigation().extras.state)
)
```
[Passing Data between Routes in Angular v7.2](https://netbasal.com/set-state-object-when-navigating-in-angular-7-2-b87c5b977bb)
[Angular Pass Data to Route: Dynamic/Static](https://www.tektutorialshub.com/angular/angular-pass-data-to-route/)
## 新分頁參數傳遞
> 目前只用window.open()開過新分頁。
> [name=Ruby Lin]
### #Storage
將資料暫存在Local Storage裡,待新分頁取得值之後,再將Local Storage裡的資料刪除。
:::info
🔔 node_module> typescript> lib> lib.dom.d.ts 有提供 Storage存取的方法。
:::
#### 儲存資料至Storage
```typescript
// localStorage.setItem(key: string, value: string);
localStorage.setItem('passDataByStorage', JSON.stringify(dataList));
```
可在 `DevTools(F12)> Application> Local Storage` 檢查是否有成功儲存

#### 從Storage取得資料
```typescript
// localStorage.getItem(key: string)
localStorage.getItem('passDataByStorage')
// string轉成Json格式
JSON.parse(localStorage.getItem('passDataByStorage'))
```
:::warning
💡 將資料暫存到Storage,又馬上刪除Storage並不是很好的方法,因為Storage可以用來存更重要的資訊,且如果這樣的做法需要大量被使用,又更不好了。
:::
### #BroadcastChannel
>此方法撰寫上比較複雜,本身對此觀念也比較不足,可以再多看一些專案在使用
> [name=Ruby Lin]
使用匯入的.js檔
broadcast-channel.js
```javascript=
(function (global) {
var channels = [];
function BroadcastChannel(channel) {
var $this = this;
channel = String(channel);
var id = '$BroadcastChannel$' + channel + '$';
channels[id] = channels[id] || [];
channels[id].push(this);
this._name = channel;
this._id = id;
this._closed = false;
this._mc = new MessageChannel();
this._mc.port1.start();
this._mc.port2.start();
global.addEventListener('storage', function (e) {
if (e.storageArea !== global.localStorage) return;
if (e.newValue === null) return;
if (e.key.substring(0, id.length) !== id) return;
var data = e.newValue;
try {
data = JSON.parse(e.newValue);
} catch (error) {
console.error('global.addEventListener() parse e.newValue error, e.newValue = ' + ';' + e.newValue, error);
}
$this._mc.port2.postMessage(data);
});
}
BroadcastChannel.prototype = {
// BroadcastChannel API
get name() { return this._name; },
postMessage: function (message) {
var $this = this;
if (this._closed) {
var e = new Error();
e.name = 'InvalidStateError';
throw e;
}
var value = JSON.stringify(message);
// Broadcast to other contexts via storage events...
var key = this._id + String(Date.now()) + '$' + String(Math.random());
global.localStorage.setItem(key, value);
setTimeout(function () { global.localStorage.removeItem(key); }, 500);
// Broadcast to current context via ports
channels[this._id].forEach(function (bc) {
if (bc === $this) return;
bc._mc.port2.postMessage(JSON.parse(value));
});
},
close: function () {
if (this._closed) return;
this._closed = true;
this._mc.port1.close();
this._mc.port2.close();
var index = channels[this._id].indexOf(this);
channels[this._id].splice(index, 1);
},
// EventTarget API
get onmessage() { return this._mc.port1.onmessage; },
set onmessage(value) { this._mc.port1.onmessage = value; },
addEventListener: function (type, listener /*, useCapture*/) {
return this._mc.port1.addEventListener.apply(this._mc.port1, arguments);
},
removeEventListener: function (type, listener /*, useCapture*/) {
return this._mc.port1.removeEventListener.apply(this._mc.port1, arguments);
},
dispatchEvent: function (event) {
return this._mc.port1.dispatchEvent.apply(this._mc.port1, arguments);
}
};
global.BroadcastChannel = global.BroadcastChannel || BroadcastChannel;
}(self));
```
1. 當前頁註冊一個BroadcastChannel
```typescript
const broadcastChannel = new BroadcastChannel('ruby');
```
2. 新分頁也註冊一樣的BroadcastChannel
```typescript
const broadcastChannel = new BroadcastChannel('ruby');
```
3. 新分頁在元件生成後,發送訊息
```typescript
broadcastChannel.postMessage('Complete');
```
4. 當頁設置監聽事件,收到新分頁的訊息後,傳送參數(訊息)
```typescript
broadcastChannel.addEventListener('message', (messageEvent) => {
// 收到完成訊息,傳遞參數
if (messageEvent.data === 'Complete') {
const params = [{Id: 'rb001', Name: 'ruby'}];
broadcastChannel.postMessage(params);
}
});
```
5. 新分頁設置監聽事件,接收當頁傳送的參數
```typescript
broadcastChannel.addEventListener('message', (messageEvent) => {
// 接收參數
const id = messageEvent.data[0].Id;
const name = messageEvent.data[0].Name;
});
```
:::success
**📝備註**
* const broadcastChannel = new BroadcastChannel(name: string);
兩邊的name需要一致
* 當前頁等新分頁傳送Complete後,才能傳送參數。
若當前頁直接傳送參數,可能會在新分頁生成前送出參數,新分頁就收不到參數。
但也有可能有另外正確的寫法。
:::
當前頁完整程式
```typescript=
window.open(location.protocol + '//' + location.host + '/#/main' + '/report/otherExpenseList', '_blank');
const broadcastChannel = new BroadcastChannel('ruby');
broadcastChannel.addEventListener('message', (messageEvent) => {
// console.log('this.broadcastChannel.onmessage() messageEvent = ', messageEvent);
console.log('balanceList = ', messageEvent);
if (messageEvent.data === 'Complete') {
const params = [{Id: 'rb001', Name: 'ruby'}];
broadcastChannel.postMessage(params);
}
});
```
新分頁完整程式
```typescript=
const broadcastChannel = new BroadcastChannel('ruby');
broadcastChannel.postMessage('Complete');
broadcastChannel.addEventListener('message', (messageEvent) => {
// console.log('this.broadcastChannel.onmessage() messageEvent = ', messageEvent);
console.log('otherExpenseList = ', messageEvent);
console.log('otherExpenseList Id = ', messageEvent.data[0].Id );
console.log('otherExpenseList Id = ', messageEvent.data[0].Name);
});
```
### #URL Params
開新分頁時使用`window.open()`(此方法不一定要傳參數),若使用此方法有帶入第一個參數(第一個可帶入參數是url(網址)),那麼開啟的新分頁就會轉跳此網址頁。
此方法是可以帶本頁要給新分頁的參數,只要將參數調整成`?params=x1 & params2=x2`此格式,並在網址後加上。
```typescript
const url = `#/main/basic/customer?x1=${obj.x1}&x2=${obj.x2}`;
window.open(url, "_blank");
```
這樣在新分頁取得網址後,就可以將?後的字串自己分割拿來使用。
```typescript
const url = window.location.href;
// https://a1web-dev2.azurewebsites.net/#/main/basic/customer?x1=123&x2=456
```
:::success
**📝備註**
* 優點
* 簡單
* 缺點
* 網址過長,可能會造成使用者體驗不佳。
:::