---
# System prepended metadata

title: "\U0001F31D[T]Passing Parameters 參數傳遞"
tags: [Angular, Params]

---

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

## 單頁參數傳遞
![Input&Output](https://i.imgur.com/XwcjRob.png)

### #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](https://i.imgur.com/RLNNNN5.png)

#### 從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
**📝備註**
* 優點
    * 簡單
* 缺點
    * 網址過長，可能會造成使用者體驗不佳。
:::