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