Create outputs table screen

Allow to declare a property as output parameter
from the Properties Assignment screen on a Service.

Issue-ID: SDC-3319
Signed-off-by: aribeiro <anderson.ribeiro@est.tech>
Change-Id: I555839df094d391f9991ee1248eed93e3a1b24c9
diff --git a/catalog-ui/src/app/models.ts b/catalog-ui/src/app/models.ts
index ad201e2..389c756 100644
--- a/catalog-ui/src/app/models.ts
+++ b/catalog-ui/src/app/models.ts
@@ -56,6 +56,7 @@
 export * from './models/properties-inputs/property-declare-api-model';
 export * from './models/properties-inputs/property-input-detail';
 export * from './models/properties-inputs/input-fe-model';
+export * from './models/properties-inputs/output-fe-model';
 export * from './models/properties-inputs/simple-flat-property';
 export * from './models/data-types-map';
 export * from './models/data-types';
@@ -119,6 +120,7 @@
 export * from './models/radio-button';
 export * from './models/filter-properties-assignment-data';
 export * from './models/properties-inputs/input-be-model';
+export * from './models/properties-inputs/output-be-model';
 export * from './models/catalogSelector';
 export * from './models/componentsInstances/fullComponentInstance';
 export * from './models/catalogSelector';
@@ -127,4 +129,4 @@
 export * from './models/relationship-types';
 export * from './models/tosca-presentation';
 export * from './models/node-types';
-export * from './models/capability-types';
\ No newline at end of file
+export * from './models/capability-types';
diff --git a/catalog-ui/src/app/models/properties-inputs/output-be-model.ts b/catalog-ui/src/app/models/properties-inputs/output-be-model.ts
new file mode 100644
index 0000000..f2a14eb
--- /dev/null
+++ b/catalog-ui/src/app/models/properties-inputs/output-be-model.ts
@@ -0,0 +1,46 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+import {PropertyBEModel} from 'app/models';
+
+export class OutputBEModel extends PropertyBEModel {
+
+  outputPath: string;
+  outputs: Array<OutputComponentInstanceModel>;
+  instanceUniqueId: string;
+  ownerId: string;
+  propertyId: string;
+  properties: Array<OutputComponentInstanceModel>;
+
+  constructor(output?: OutputBEModel) {
+    super(output);
+    this.instanceUniqueId = output.instanceUniqueId;
+    this.propertyId = output.propertyId;
+    this.properties = output.properties;
+    this.outputs = output.outputs;
+    this.ownerId = output.ownerId;
+    this.outputPath = output.outputPath;
+  }
+
+}
+
+export interface OutputComponentInstanceModel extends OutputBEModel {
+  componentInstanceId: string;
+  componentInstanceName: string;
+}
diff --git a/catalog-ui/src/app/models/properties-inputs/output-fe-model.ts b/catalog-ui/src/app/models/properties-inputs/output-fe-model.ts
new file mode 100644
index 0000000..19eebb1
--- /dev/null
+++ b/catalog-ui/src/app/models/properties-inputs/output-fe-model.ts
@@ -0,0 +1,88 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+import * as _ from "lodash";
+import {PropertyFEModel} from "../../models";
+import {PROPERTY_DATA} from "../../utils/constants";
+import {DerivedPropertyType} from "./property-be-model";
+import {OutputBEModel} from "./output-be-model";
+
+export class OutputFeModel extends OutputBEModel {
+  isSimpleType: boolean;
+  relatedPropertyValue: any;
+  relatedPropertyName: string;
+  defaultValueObj: any;
+  defaultValueObjIsValid: boolean;
+  defaultValueObjOrig: any;
+  defaultValueObjIsChanged: boolean;
+  derivedDataType: DerivedPropertyType;
+  requiredOrig: boolean;
+
+  constructor(output?: OutputBEModel) {
+    super(output);
+    if (output) {
+      this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1;
+      let relatedProperty = output.properties && output.properties[0] || output.outputs && output.outputs[0];
+      if (relatedProperty) {
+        this.relatedPropertyValue = relatedProperty.value;
+        this.relatedPropertyName = relatedProperty.name;
+      }
+      this.derivedDataType = this.getDerivedPropertyType();
+      this.resetDefaultValueObjValidation();
+      this.updateDefaultValueObjOrig();
+
+      this.requiredOrig = this.required;
+    }
+  }
+
+  public updateDefaultValueObj(defaultValueObj: any, isValid: boolean) {
+    this.defaultValueObj = PropertyFEModel.cleanValueObj(defaultValueObj);
+    this.defaultValueObjIsValid = isValid;
+    this.defaultValueObjIsChanged = this.hasDefaultValueChanged();
+  }
+
+  public updateDefaultValueObjOrig() {
+    this.defaultValueObjOrig = _.cloneDeep(this.defaultValueObj);
+    this.defaultValueObjIsChanged = false;
+  }
+
+  public getJSONDefaultValue(): string {
+    return PropertyFEModel.stringifyValueObj(this.defaultValueObj, this.schema.property.type, this.derivedDataType);
+  }
+
+  public getDefaultValueObj(): any {
+    return PropertyFEModel.parseValueObj(this.defaultValue, this.type, this.derivedDataType);
+  }
+
+  public resetDefaultValueObjValidation() {
+    this.defaultValueObjIsValid = true;
+  }
+
+  hasDefaultValueChanged(): boolean {
+    return !_.isEqual(this.defaultValueObj, this.defaultValueObjOrig);
+  }
+
+  hasRequiredChanged(): boolean {
+    return this.required !== this.requiredOrig;
+  }
+
+  hasChanged(): boolean {
+    return this.hasDefaultValueChanged() || this.hasRequiredChanged();
+  }
+}
diff --git a/catalog-ui/src/app/models/properties-inputs/property-be-model.ts b/catalog-ui/src/app/models/properties-inputs/property-be-model.ts
index 1d263bd..952e486 100644
--- a/catalog-ui/src/app/models/properties-inputs/property-be-model.ts
+++ b/catalog-ui/src/app/models/properties-inputs/property-be-model.ts
@@ -22,6 +22,7 @@
 import { SchemaProperty, SchemaPropertyGroupModel } from '../aschema-property';
 import { ToscaPresentationData } from '../tosca-presentation';
 import { PropertyInputDetail } from './property-input-detail';
+import {PropertyOutputDetail} from "./property-output-detail";
 
 export enum DerivedPropertyType {
     SIMPLE,
@@ -48,6 +49,7 @@
     description: string;
     fromDerived: boolean;
     getInputValues: PropertyInputDetail[];
+    getOutputValues: PropertyOutputDetail[];
     getPolicyValues: PropertyPolicyDetail[];
     name: string;
     origName: string;
@@ -61,7 +63,9 @@
     value: string;
     parentPropertyType: string;
     subPropertyInputPath: string;
+    subPropertyOutputPath: string;
     inputPath: string;
+    outputPath: string;
     toscaPresentation: ToscaPresentationData;
 
     constructor(property?: PropertyBEModel) {
@@ -82,11 +86,14 @@
             this.value = property.value;
             this.definition = property.definition;
             this.getInputValues = property.getInputValues;
+            this.getOutputValues = property.getOutputValues;
             this.parentPropertyType = property.parentPropertyType;
             this.subPropertyInputPath = property.subPropertyInputPath;
+            this.subPropertyOutputPath = property.subPropertyOutputPath;
             this.toscaPresentation = property.toscaPresentation;
             this.getPolicyValues = property.getPolicyValues;
             this.inputPath = property.inputPath;
+            this.outputPath = property.outputPath;
         }
 
         if (!this.schema || !this.schema.property) {
diff --git a/catalog-ui/src/app/models/properties-inputs/property-detail.ts b/catalog-ui/src/app/models/properties-inputs/property-detail.ts
new file mode 100644
index 0000000..1714aec
--- /dev/null
+++ b/catalog-ui/src/app/models/properties-inputs/property-detail.ts
@@ -0,0 +1,25 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+export class PropertyDetail {
+    inputId: string;
+    inputName: string;
+    inputPath: string;
+    list: boolean;
+}
diff --git a/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts b/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts
index de943fc..c2ce7d4 100644
--- a/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts
+++ b/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts
@@ -32,6 +32,7 @@
 export class InstancePropertiesAPIMap {
     componentInstanceProperties: InstanceBePropertiesMap;
     componentInstanceInputsMap: InstanceBePropertiesMap;
+    componentInstanceOutputsMap: InstanceBePropertiesMap;
     groupProperties: InstanceBePropertiesMap;
     policyProperties: InstanceBePropertiesMap;
 
diff --git a/catalog-ui/src/app/models/properties-inputs/property-input-detail.ts b/catalog-ui/src/app/models/properties-inputs/property-input-detail.ts
index 8c1028c..8a37a21 100644
--- a/catalog-ui/src/app/models/properties-inputs/property-input-detail.ts
+++ b/catalog-ui/src/app/models/properties-inputs/property-input-detail.ts
@@ -18,9 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-export class PropertyInputDetail {
-    inputId: string;
-    inputName: string;
-    inputPath: string;
-    list: boolean;
+import {PropertyDetail} from "./property-detail";
+
+export class PropertyInputDetail extends PropertyDetail {
 }
diff --git a/catalog-ui/src/app/models/properties-inputs/property-output-detail.ts b/catalog-ui/src/app/models/properties-inputs/property-output-detail.ts
new file mode 100644
index 0000000..da6c119
--- /dev/null
+++ b/catalog-ui/src/app/models/properties-inputs/property-output-detail.ts
@@ -0,0 +1,23 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+import {PropertyDetail} from "./property-detail";
+
+export class PropertyOutputDetail extends PropertyDetail {
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.html b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.html
new file mode 100644
index 0000000..cd3f428
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.html
@@ -0,0 +1,91 @@
+<!--
+============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+-->
+
+<div class="output-properties-table">
+  <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+  <div class="table-header">
+    <div class="table-cell col-output-property-name" (click)="sort('name')">Property Name
+      <span *ngIf="sortBy === 'name'" class="table-header-sort-arrow" [ngClass]="{'down': reverse, 'up':!reverse}"></span>
+    </div>
+    <div class="table-cell col-output-property-instance" (click)="sort('instanceUniqueId')">From Instance
+      <span *ngIf="sortBy === 'instanceUniqueId'" class="table-header-sort-arrow" [ngClass]="{'down': reverse, 'up':!reverse}"></span>
+    </div>
+    <div class="table-cell col-output-property-type" (click)="sort('type')">Type
+      <span *ngIf="sortBy === 'type'" class="table-header-sort-arrow" [ngClass]="{'down': reverse, 'up':!reverse}">
+            </span>
+    </div>
+    <div class="table-cell col-output-properties-required" (click)="sort('required')" *ngIf="componentType == 'SERVICE'">
+      <span tooltip="Required in Runtime" tooltipDelay="400">Req. in RT</span>
+    </div>
+    <div class="table-cell col-output-property-value">Value</div>
+  </div>
+  <div class="table-body">
+    <div class="no-data" *ngIf="!outputs || !outputs.length">No data to display</div>
+    <div>
+      <div class="table-row" *ngFor="let output of outputs" (click)="selectedOutputId = output.path" [ngClass]="{'selected': selectedOutputId && selectedOutputId === output.path}">
+        <!-- Property Name -->
+        <div class="table-cell col-output-property-name">
+          <div class="output-inner-cell-div">
+            <span class="property-name" tooltip="{{output.name}}">{{output.name}}</span>
+          </div>
+          <span *ngIf="output.description"
+                class="property-description-icon sprite-new show-desc"
+                tooltip="{{output.description}}" tooltipDelay="0"></span>
+        </div>
+        <!-- From Instance -->
+        <div class="table-cell col-output-property-instance">
+          <div class="output-inner-cell-div" tooltip="{{instanceNamesMap[output.instanceUniqueId]?.name}}">
+            <span>{{instanceNamesMap[output.instanceUniqueId]?.name}}</span>
+          </div>
+        </div>
+        <!-- Type -->
+        <div class="table-cell col-output-property-type">
+          <div class="output-inner-cell-div" tooltip="{{output.type | contentAfterLastDot}}">
+            <span>{{output.type | contentAfterLastDot}}</span>
+          </div>
+        </div>
+        <!-- Required in runtime -->
+        <div class="table-cell col-output-properties-required" *ngIf="componentType == 'SERVICE'">
+          <sdc-checkbox [(checked)]="output.required"
+                        (checkedChange)="onRequiredChanged(output, $event)"
+                        [disabled]="readonly"></sdc-checkbox>
+        </div>
+        <!-- Value -->
+        <div class="table-cell col-output-property-value output-value-col" [class.inner-table-container]="output.childrenProperties || !output.isSimpleType">
+          <dynamic-element class="value-output"
+                           *ngIf="checkInstanceFePropertiesMapIsFilled() && output.isSimpleType"
+                           pattern="null"
+                           [value]="output.value"
+                           [type]="string"
+                           [name]="output.name"
+                           (elementChanged)="onOutputChanged(output, $event)"
+                           [readonly]="true"
+                           [testId]="'output-' + output.name"
+                           [constraints] = "getConstraints(output)">
+          </dynamic-element>
+          <div class="delete-button-container">
+            <span *ngIf="output.instanceUniqueId && !readonly" class="sprite-new delete-btn" (click)="openDeleteModal(output)"></span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+
diff --git a/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.less b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.less
new file mode 100644
index 0000000..b86719e
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.less
@@ -0,0 +1,189 @@
+@import './../../../../../assets/styles/variables.less';
+
+:host /deep/ output {
+  width: 100%;
+}
+
+.output-properties-table {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  height: 100%;
+  text-align: left;
+
+  .output-inner-cell-div {
+    max-width: 100%;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    height: 20px;
+  }
+
+
+  .table-header {
+    font-weight: bold;
+    border-top: #d2d2d2 solid 1px;
+    background-color: #eaeaea;
+    color: #191919;
+
+    .table-cell {
+      font-size: 13px;
+
+      .table-header-sort-arrow {
+        display: inline-block;
+        background-color: transparent;
+        border: none;
+        color: #AAA;
+        margin: 8px 0 0 5px;
+
+        &.up {
+          border-left: 5px solid transparent;
+          border-right: 5px solid transparent;
+          border-bottom: 5px solid;
+        }
+
+        &.down {
+          border-left: 5px solid transparent;
+          border-right: 5px solid transparent;
+          border-top: 5px solid;
+        }
+      }
+    }
+
+    .output-value-col {
+      justify-content: flex-start;
+      padding: 10px;
+    }
+  }
+
+  .table-header, .table-row {
+    display: flex;
+    flex-direction: row;
+    flex: 0 0 auto;
+  }
+
+  .table-body {
+    display: flex;
+    flex-direction: column;
+    overflow-y: auto;
+    flex: 1;
+
+    .no-data {
+      border: #d2d2d2 solid 1px;
+      border-top: none;
+      text-align: center;
+      height: 100%;
+      padding: 20px;
+    }
+
+    /deep/ .selected {
+      background-color: #e6f6fb;
+      color: #009fdb;
+    }
+  }
+
+  .table-row {
+    &:hover {
+      background-color: #f8f8f8;
+      cursor: pointer;
+    }
+
+    &:last-child {
+      flex: 1 0 auto;
+    }
+
+    .selected-row {
+      background-color: #e6f6fb;
+    }
+  }
+
+  .table-cell {
+    font-size: 13px;
+    flex: 1;
+    border: #d2d2d2 solid 1px;
+    border-right: none;
+    border-top: none;
+    padding: 10px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+
+
+    &:last-child {
+      border-right: #d2d2d2 solid 1px;
+    }
+
+    &.col-output-property-name {
+      flex: 1 0 130px;
+      max-width: 250px;
+
+      justify-content: space-between;
+
+      .property-name {
+        flex: 1;
+      }
+
+      .property-description-icon {
+        float: right;
+        margin-top: 4px;
+        margin-left: 5px;
+        flex: 0 0 auto;
+      }
+    }
+
+    &.col-output-property-type {
+      flex: 0 0 140px;
+      max-width: 140px;
+    }
+
+    &.col-output-property-instance {
+      flex: 0 0 120px;
+      max-width: 120px;
+    }
+
+    &.col-output-properties-required {
+      flex: 0 0 80px;
+      max-width: 80px;
+      text-align: center;
+    }
+
+    &.col-output-property-value {
+      .value-output {
+        flex: 1;
+        border: none;
+        background-color: inherit;
+
+        &:focus, &:active {
+          border: none;
+          outline: none;
+        }
+      }
+
+      .delete-btn {
+        flex: 0 0 auto;
+      }
+
+      .delete-button-container {
+        max-height: 24px;
+      }
+
+      &.inner-table-container {
+        padding: 0px;
+
+        .delete-button-container {
+          padding: 0 8px 0 0;
+        }
+      }
+    }
+
+    &.output-value-col {
+      padding: 8px;
+    }
+
+  }
+
+  .filtered {
+    /deep/ .checkbox-label-content {
+      background-color: yellow;
+    }
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.ts b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.ts
new file mode 100644
index 0000000..5ea98a6
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/outputs-table/outputs-table.component.ts
@@ -0,0 +1,137 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {OutputFeModel} from "app/models";
+import {InstanceFeDetails} from "../../../../models/instance-fe-details";
+import {InstanceFePropertiesMap} from "../../../../models/properties-inputs/property-fe-map";
+import {ModalService} from "../../../services/modal.service";
+import {DataTypeService} from "../../../services/data-type.service";
+import {TranslateService} from "../../../shared/translator/translate.service";
+
+@Component({
+  selector: 'outputs-table',
+  templateUrl: './outputs-table.component.html',
+  styleUrls: ['./outputs-table.component.less']
+})
+export class OutputsTableComponent implements OnInit {
+
+  @Input() outputs: Array<OutputFeModel>;
+  @Input() instanceNamesMap: Map<string, InstanceFeDetails>;
+  @Input() readonly: boolean;
+  @Input() isLoading: boolean;
+  @Input() componentType: string;
+  @Output() outputChanged: EventEmitter<any> = new EventEmitter<any>();
+  @Output() deleteOutput: EventEmitter<any> = new EventEmitter<any>();
+  @Input() fePropertiesMap: InstanceFePropertiesMap;
+
+  deleteMsgTitle: string;
+  deleteMsgBodyTxt: string;
+  modalDeleteBtn: string;
+  modalCancelBtn: string;
+  sortBy: String;
+  reverse: boolean;
+  selectedOutputToDelete: OutputFeModel;
+
+  constructor(private modalService: ModalService,
+              private dataTypeService: DataTypeService,
+              private translateService: TranslateService) {
+  }
+
+  ngOnInit() {
+    this.translateService.languageChangedObservable.subscribe((lang) => {
+      this.deleteMsgTitle = this.translateService.translate('DELETE_OUTPUT_TITLE');
+      this.modalDeleteBtn = this.translateService.translate('MODAL_DELETE');
+      this.modalCancelBtn = this.translateService.translate('MODAL_CANCEL');
+
+    });
+  }
+
+  sort = (sortBy) => {
+    this.reverse = (this.sortBy === sortBy) ? !this.reverse : true;
+    let reverse = this.reverse ? 1 : -1;
+    this.sortBy = sortBy;
+    let instanceNameMapTemp = this.instanceNamesMap;
+    let itemIdx1Val = "";
+    let itemIdx2Val = "";
+    this.outputs.sort(function (itemIdx1, itemIdx2) {
+      if (sortBy == 'instanceUniqueId') {
+        itemIdx1Val = (itemIdx1[sortBy] && instanceNameMapTemp[itemIdx1[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx1[sortBy]].name : "";
+        itemIdx2Val = (itemIdx2[sortBy] && instanceNameMapTemp[itemIdx2[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx2[sortBy]].name : "";
+      } else {
+        itemIdx1Val = itemIdx1[sortBy];
+        itemIdx2Val = itemIdx2[sortBy];
+      }
+      if (itemIdx1Val < itemIdx2Val) {
+        return -1 * reverse;
+      } else if (itemIdx1Val > itemIdx2Val) {
+        return 1 * reverse;
+      } else {
+        return 0;
+      }
+    });
+  };
+
+  onOutputChanged = (output, event) => {
+    output.updateDefaultValueObj(event.value, event.isValid);
+    this.outputChanged.emit(output);
+  };
+
+  onRequiredChanged = (output: OutputFeModel, event) => {
+    this.outputChanged.emit(output);
+  }
+
+  onDeleteOutput = () => {
+    this.deleteOutput.emit(this.selectedOutputToDelete);
+    this.modalService.closeCurrentModal();
+  };
+
+  openDeleteModal = (output: OutputFeModel) => {
+    this.selectedOutputToDelete = output;
+    this.translateService.languageChangedObservable.subscribe((lang) => {
+      this.deleteMsgBodyTxt = this.translateService.translate('DELETE_OUTPUT_MSG',{outputName: output.name});
+      this.modalService.createActionModal(this.deleteMsgTitle, this.deleteMsgBodyTxt,
+          this.modalDeleteBtn, this.onDeleteOutput, this.modalCancelBtn).instance.open();
+    });
+  }
+
+  getConstraints(output: OutputFeModel): string[] {
+    if (output.outputPath) {
+      const pathValuesName = output.outputPath.split('#');
+      const rootPropertyName = pathValuesName[0];
+      const propertyName = pathValuesName[1];
+      let filteredRootPropertyType = _.values(this.fePropertiesMap)[0].filter(property =>
+          property.name == rootPropertyName);
+      if (filteredRootPropertyType.length > 0) {
+        let rootPropertyType = filteredRootPropertyType[0].type;
+        return this.dataTypeService.getConstraintsByParentTypeAndUniqueID(rootPropertyType, propertyName);
+      } else {
+        return null;
+      }
+
+    } else {
+      return null;
+    }
+  }
+
+  checkInstanceFePropertiesMapIsFilled() {
+    return _.keys(this.fePropertiesMap).length > 0
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts
index 3def63e..408f562 100644
--- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts
+++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts
@@ -36,11 +36,14 @@
 import {ComponentModeService} from "../../services/component-services/component-mode.service";
 import {SdcUiComponentsModule} from "onap-ui-angular";
 import {ModalFormsModule} from "app/ng2/components/ui/forms/modal-forms.module";
+import {OutputsUtils} from "./services/outputs.utils";
+import {OutputsTableComponent} from "../../components/logic/outputs-table/outputs-table.component";
 
 @NgModule({
     declarations: [
         PropertiesAssignmentComponent,
         InputsTableComponent,
+        OutputsTableComponent,
         HierarchyNavigationComponent,
         FilterPropertiesAssignmentComponent
     ],
@@ -52,13 +55,14 @@
         PoliciesTableModule,
         UiElementsModule,
         SdcUiComponentsModule,
-        ModalFormsModule],
+        ModalFormsModule
+    ],
 
     entryComponents: [PropertiesAssignmentComponent],
     exports: [
         PropertiesAssignmentComponent
     ],
-    providers: [PropertiesService, HierarchyNavService, PropertiesUtils, InputsUtils, DataTypeService, ComponentModeService]
+    providers: [PropertiesService, HierarchyNavService, PropertiesUtils, InputsUtils, OutputsUtils, DataTypeService, ComponentModeService]
 })
 export class PropertiesAssignmentModule {
 
diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html
index 6856ae8..6a3d57e 100644
--- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html
+++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
- 
+
 <div class="properties-assignment-page">
     <div class="main-content">
         <div class="left-column">
@@ -52,6 +52,18 @@
                             (inputChanged)="dataChanged($event)">
                         </inputs-table>
                     </tab>
+                    <tab tabTitle="Outputs">
+                        <outputs-table class="properties-table"
+                                      [fePropertiesMap]="instanceFePropertiesMap"
+                                      [readonly]="isReadonly"
+                                      [outputs]="outputs | searchFilter:'name':searchQuery"
+                                      [instanceNamesMap]="componentInstanceNamesMap"
+                                      [isLoading]="loadingOutputs"
+                                      [componentType]="component.componentType"
+                                      (deleteOutput)="deleteOutput($event)"
+                                      (outputChanged)="dataChanged($event)">
+                        </outputs-table>
+                    </tab>
                     <tab tabTitle="Policies">
                         <policies-table class="properties-table"
                                       [readonly]="isReadonly"
@@ -68,13 +80,14 @@
                 </div>
             </div>
             <div class="header">
-                <div class="search-filter-container" [class.without-filter]="isInputsTabSelected || isPoliciesTabSelected">
+                <div class="search-filter-container" [class.without-filter]="isInputsTabSelected || isPoliciesTabSelected || isOutputsTabSelected">
                     <span *ngIf="displayClearSearch && isPropertiesTabSelected" (click)="clickOnClearSearch()" class="clear-filter" data-tests-id="clear-filter-button">Clear All</span>
                     <input type="text" class="search-box" placeholder="Search" [(ngModel)]="searchQuery" data-tests-id="search-box"/>
                     <span class="sprite search-icon" data-tests-id="search-button"></span>
                     <filter-properties-assignment *ngIf="isPropertiesTabSelected" #advanceSearch class="advance-search" [componentType]="component.componentType" (searchProperties)="searchPropertiesInstances($event)"></filter-properties-assignment>
                 </div>
-                <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData" (click)="declareProperties()" data-tests-id="declare-button declare-input">Declare Input</button>
+                <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || hasChangedData" (click)="declareProperties(input)" data-tests-id="declare-button declare-input">Declare Input</button>
+                <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData || isSelf()" (click)="declareProperties(output)" data-tests-id="declare-button declare-output">Declare Output</button>
                 <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData || isSelf()" (click)="declarePropertiesToPolicies()" data-tests-id="declare-button declare-policy">Declare Policy</button>
                 <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || checkedChildPropertiesCount || isReadonly || hasChangedData" (click)="declareListProperties()" data-tests-id="declare-button declare-list-input">Create List Input</button>
             </div>
@@ -89,10 +102,10 @@
                         <div class="hierarchy-header white-sub-header">
                             <span tooltip="{{component.name}}">{{component.name}}</span>
                         </div>
-                        <div *ngIf="!instancesNavigationData || instancesNavigationData.length === 0 || isInputsTabSelected || isPoliciesTabSelected">No data to display</div>
+                        <div *ngIf="!instancesNavigationData || instancesNavigationData.length === 0 || isInputsTabSelected || isOutputsTabSelected || isPoliciesTabSelected">No data to display</div>
                         <hierarchy-navigation class="hierarchy-nav"
                                 (updateSelected)="onInstanceSelectedUpdate($event)"
-                                [displayData]="isInputsTabSelected || isPoliciesTabSelected ? []: instancesNavigationData"
+                                [displayData]="isInputsTabSelected || isOutputsTabSelected || isPoliciesTabSelected ? []: instancesNavigationData"
                                 [selectedItem]="selectedInstanceData?.uniqueId"
                                 [displayOptions]="hierarchyInstancesDisplayOptions"></hierarchy-navigation>
                     </div>
@@ -102,10 +115,10 @@
                     <div class="hierarchy-header white-sub-header" [class.selected]="selectedFlatProperty.path == propertyStructureHeader">
                         <span tooltip="{{isPropertiesTabSelected ? propertyStructureHeader : ''}}">{{isPropertiesTabSelected ? (propertyStructureHeader || "No Property Selected") : "No Property Selected"}}</span>
                     </div>
-                    <div *ngIf="!propertiesNavigationData || propertiesNavigationData.length === 0 || isInputsTabSelected || isPoliciesTabSelected">No data to display</div>
+                    <div *ngIf="!propertiesNavigationData || propertiesNavigationData.length === 0 || isInputsTabSelected || isOutputsTabSelected || isPoliciesTabSelected">No data to display</div>
                     <hierarchy-navigation class="hierarchy-nav"
                             (updateSelected)="onPropertySelectedUpdate($event)"
-                            [displayData]="isInputsTabSelected || isPoliciesTabSelected ? [] : propertiesNavigationData"
+                            [displayData]="isInputsTabSelected || isOutputsTabSelected || isPoliciesTabSelected ? [] : propertiesNavigationData"
                             [selectedItem]="selectedFlatProperty.path"
                             [displayOptions]="hierarchyPropertiesDisplayOptions"></hierarchy-navigation>
                     </div>
diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts
index 9fb1a92..8da05af 100644
--- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts
+++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts
@@ -26,7 +26,7 @@
 import { ComponentServiceNg2 } from "../../services/component-services/component.service";
 import { TopologyTemplateService } from "../../services/component-services/topology-template.service";
 import { ComponentInstanceServiceNg2 } from "../../services/component-instance-services/component-instance.service"
-import { InputBEModel, InputFEModel, ComponentInstance, GroupInstance, PolicyInstance, PropertyBEModel, DerivedFEProperty, SimpleFlatProperty } from "app/models";
+import { InputBEModel, OutputBEModel, InputFEModel, OutputFeModel, ComponentInstance, GroupInstance, PolicyInstance, PropertyBEModel, DerivedFEProperty, SimpleFlatProperty } from "app/models";
 import { KeysPipe } from 'app/ng2/pipes/keys.pipe';
 import { WorkspaceMode, EVENTS, PROPERTY_TYPES } from "../../../utils/constants";
 import { EventListenerService } from "app/services/event-listener-service"
@@ -47,14 +47,17 @@
 import { CapabilitiesGroup, Capability } from "../../../models/capability";
 import { ToscaPresentationData } from "../../../models/tosca-presentation";
 import { Observable } from "rxjs";
+import {OutputsUtils} from "./services/outputs.utils";
+import {TranslateService} from "../../shared/translator/translate.service";
 
 const SERVICE_SELF_TITLE = "SELF";
+
 @Component({
     templateUrl: './properties-assignment.page.component.html',
     styleUrls: ['./properties-assignment.page.component.less']
 })
 export class PropertiesAssignmentComponent {
-    title = "Properties & Inputs";
+    title = "Properties, Inputs & Outputs";
 
     component: ComponentData;
     componentInstanceNamesMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>();//instanceUniqueId, {name, iconClass}
@@ -64,6 +67,7 @@
 
     instanceFePropertiesMap:InstanceFePropertiesMap;
     inputs: Array<InputFEModel> = [];
+    outputs: Array<OutputFeModel> = [];
     policies: Array<PolicyInstance> = [];
     instances: Array<ComponentInstance|GroupInstance|PolicyInstance> = [];
     searchQuery: string;
@@ -80,15 +84,17 @@
     searchPropertyName:string;
     currentMainTab:Tab;
     isInputsTabSelected:boolean;
+    isOutputsTabSelected:boolean;
     isPropertiesTabSelected:boolean;
     isPoliciesTabSelected:boolean;
     isReadonly:boolean;
     resourceIsReadonly:boolean;
     loadingInstances:boolean = false;
     loadingInputs:boolean = false;
+    loadingOutputs:boolean = false;
     loadingPolicies:boolean = false;
     loadingProperties:boolean = false;
-    changedData:Array<PropertyFEModel|InputFEModel>;
+    changedData:Array<PropertyFEModel|InputFEModel|OutputFeModel>;
     hasChangedData:boolean;
     isValidChangedData:boolean;
     savingChangedData:boolean;
@@ -96,6 +102,13 @@
     serviceBePropertiesMap: InstanceBePropertiesMap;
     serviceBeCapabilitiesPropertiesMap: InstanceBePropertiesMap;
     selectedInstance_FlattenCapabilitiesList: Capability[];
+    input = "input";
+    output = "output";
+
+    alertMsgTitle: string;
+    alertMsgBodyTxt: string;
+    modalDeleteBtn: string;
+    modalCancelBtn: string;
 
     @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
     @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
@@ -105,6 +118,7 @@
                 private hierarchyNavService: HierarchyNavService,
                 private propertiesUtils:PropertiesUtils,
                 private inputsUtils:InputsUtils,
+                private outputsUtils:OutputsUtils,
                 private componentServiceNg2:ComponentServiceNg2,
                 private componentInstanceServiceNg2:ComponentInstanceServiceNg2,
                 @Inject("$stateParams") _stateParams,
@@ -116,7 +130,8 @@
                 private ModalServiceSdcUI: SdcUiServices.ModalService,
                 private ModalService: ModalService,
                 private keysPipe:KeysPipe,
-                private topologyTemplateService: TopologyTemplateService) {
+                private topologyTemplateService: TopologyTemplateService,
+                private translateService: TranslateService) {
 
         this.instanceFePropertiesMap = new InstanceFePropertiesMap();
         /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time
@@ -132,7 +147,9 @@
 
     ngOnInit() {
         console.log("==>" + this.constructor.name + ": ngOnInit");
+        this.initTranslateService()
         this.loadingInputs = true;
+        this.loadingOutputs = true;
         this.loadingPolicies = true;
         this.loadingInstances = true;
         this.loadingProperties = true;
@@ -147,6 +164,17 @@
                 this.loadingInputs = false;
 
             }, error => {}); //ignore error
+        this.topologyTemplateService
+        .getComponentOutputs(this.component.componentType, this.component.uniqueId)
+        .subscribe(response => {
+            _.forEach(response.outputs, (output: OutputBEModel) => {
+                const newOutput: OutputFeModel = new OutputFeModel(output);
+                this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
+                this.outputs.push(newOutput);
+            });
+            this.loadingOutputs = false;
+
+        }, error => {}); //ignore error
         this.componentServiceNg2
             .getComponentResourcePropertiesData(this.component)
             .subscribe(response => {
@@ -190,6 +218,14 @@
         });
     };
 
+    private initTranslateService() {
+        this.translateService.languageChangedObservable.subscribe((lang) => {
+            this.alertMsgTitle = this.translateService.translate('PROPERTY_ALREADY_DECLARED');
+            this.modalDeleteBtn = this.translateService.translate('MODAL_DELETE');
+            this.modalCancelBtn = this.translateService.translate('MODAL_CANCEL');
+        });
+    }
+
     ngOnDestroy() {
         this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
         this.stateChangeStartUnregister();
@@ -344,12 +380,16 @@
     };
 
     /*** VALUE CHANGE EVENTS ***/
-    dataChanged = (item:PropertyFEModel|InputFEModel) => {
+    dataChanged = (item:PropertyFEModel|InputFEModel|OutputFeModel) => {
         let itemHasChanged;
         if (this.isPropertiesTabSelected && item instanceof PropertyFEModel) {
             itemHasChanged = item.hasValueObjChanged();
         } else if (this.isInputsTabSelected && item instanceof InputFEModel) {
             itemHasChanged = item.hasChanged();
+        } else if (this.isOutputsTabSelected && item instanceof OutputFeModel) {
+            // TODO - 'save' button disabled until FE/BE support implemented
+            itemHasChanged = false;
+            // itemHasChanged = item.hasChanged();
         } else if (this.isPoliciesTabSelected && item instanceof InputFEModel) {
             itemHasChanged = item.hasDefaultValueChanged();
         }
@@ -369,6 +409,8 @@
             this.isValidChangedData = this.changedData.every((changedItem) => (<PropertyFEModel>changedItem).valueObjIsValid);
         } else if (this.isInputsTabSelected || this.isPoliciesTabSelected) {
             this.isValidChangedData = this.changedData.every((changedItem) => (<InputFEModel>changedItem).defaultValueObjIsValid);
+        } else if (this.isOutputsTabSelected) {
+            this.isValidChangedData = this.changedData.every((changedItem) => (<OutputFeModel>changedItem).defaultValueObjIsValid);
         }
         this.updateHasChangedData();
     };
@@ -441,15 +483,14 @@
         this.currentMainTab = this.propertyInputTabs.tabs.find((tab) => tab.title === event.title);
         this.isPropertiesTabSelected = this.currentMainTab.title === "Properties";
         this.isInputsTabSelected = this.currentMainTab.title === "Inputs";
+        this.isOutputsTabSelected = this.currentMainTab.title === "Outputs";
         this.isPoliciesTabSelected = this.currentMainTab.title === "Policies";
         this.propertyStructureHeader = null;
         this.searchQuery = '';
     };
 
-
-
-    /*** DECLARE PROPERTIES/INPUTS ***/
-    declareProperties = (): void => {
+    /*** DECLARE PROPERTIES/INPUTS/OUTPUTS ***/
+    declareProperties = (propertyType: string): void => {
         console.log("==>" + this.constructor.name + ": declareProperties");
 
         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
@@ -474,27 +515,27 @@
             }
         });
 
-        let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedComponentInstancesInputs, selectedComponentInstancesProperties, selectedGroupInstancesProperties, selectedPolicyInstancesProperties);
+        let propertiesToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedComponentInstancesInputs, selectedComponentInstancesProperties, selectedGroupInstancesProperties, selectedPolicyInstancesProperties);
 
 	//move changed capabilities properties from componentInstanceInputsMap obj to componentInstanceProperties
-        inputsToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId] =
-            (inputsToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId] || []).concat(
+        propertiesToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId] =
+            (propertiesToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId] || []).concat(
                 _.filter(
-                    inputsToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId],
+                    propertiesToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId],
                     (prop: PropertyBEModel) => this.isCapabilityProperty(prop)
                 )
             );
-        inputsToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId] = _.filter(
-            inputsToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId],
+        propertiesToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId] = _.filter(
+            propertiesToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId],
             prop => !this.isCapabilityProperty(prop)
         );
-        if (inputsToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId].length === 0) {
-            delete inputsToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId];
+        if (propertiesToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId].length === 0) {
+            delete propertiesToCreate.componentInstanceInputsMap[this.selectedInstanceData.uniqueId];
         }
 
         let isCapabilityPropertyChanged = false;
         _.forEach(
-            inputsToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId],
+            propertiesToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId],
             (prop: PropertyBEModel) => {
                 prop.name = prop.origName || prop.name;
                 if (this.isCapabilityProperty(prop)) {
@@ -502,24 +543,55 @@
                 }
             }
         );
-        this.topologyTemplateService
-            .createInput(this.component, inputsToCreate, this.isSelf())
-            .subscribe((response) => {
-                this.setInputTabIndication(response.length);
-                this.checkedPropertiesCount = 0;
-                this.checkedChildPropertiesCount = 0;
-                _.forEach(response, (input: InputBEModel) => {
-                    const newInput: InputFEModel = new InputFEModel(input);
-                    this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
-                    this.inputs.push(newInput);
-                    this.updatePropertyValueAfterDeclare(newInput);
-                });
-                if (isCapabilityPropertyChanged) {
-                    this.reloadInstanceCapabilities();
-                }
-            }, error => {}); //ignore error
+        if (propertyType == this.input) {
+            this.createInput(propertiesToCreate, isCapabilityPropertyChanged);
+        } else {
+            if (this.validateOutputPropertyDeclaration(propertiesToCreate)) {
+                this.createOutput(propertiesToCreate, isCapabilityPropertyChanged);
+            }
+        }
     };
 
+    private createInput(inputsToCreate: InstancePropertiesAPIMap, isCapabilityPropertyChanged: boolean) {
+        this.topologyTemplateService
+        .createInput(this.component, inputsToCreate, this.isSelf())
+        .subscribe((response) => {
+            this.setInputTabIndication(response.length);
+            this.checkedPropertiesCount = 0;
+            this.checkedChildPropertiesCount = 0;
+            _.forEach(response, (input: InputBEModel) => {
+                const newInput: InputFEModel = new InputFEModel(input);
+                this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
+                this.inputs.push(newInput);
+                this.updatePropertyValueAfterDeclare(newInput);
+            });
+            if (isCapabilityPropertyChanged) {
+                this.reloadInstanceCapabilities();
+            }
+        }, error => {
+        }); //ignore error
+    }
+
+    private createOutput(outputsToCreate: InstancePropertiesAPIMap, isCapabilityPropertyChanged: boolean) {
+        this.topologyTemplateService
+        .createOutput(this.component, outputsToCreate, this.isSelf())
+        .subscribe((response) => {
+            this.setOutputTabIndication(response.length);
+            this.checkedPropertiesCount = 0;
+            this.checkedChildPropertiesCount = 0;
+            _.forEach(response, (output: OutputBEModel) => {
+                const newOutput: OutputFeModel = new OutputFeModel(output);
+                this.outputsUtils.resetOutputDefaultValue(newOutput, output.defaultValue);
+                this.outputs.push(newOutput);
+            });
+            if (isCapabilityPropertyChanged) {
+                this.reloadInstanceCapabilities();
+            }
+        }, error => {
+            console.error("Failed to declare output with error:", error);
+        });
+    }
+
     declareListProperties = (): void => {
         console.log('declareListProperties() - enter');
 
@@ -528,6 +600,7 @@
         let selectedGroupInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
         let selectedPolicyInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
         let selectedComponentInstancesInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
+        let selectedComponentInstancesOutputs: InstanceBePropertiesMap = new InstanceBePropertiesMap();
         let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []);
         let propertyNameList: Array<string> = [];
         let insId :string;
@@ -796,6 +869,25 @@
                     });
                     console.log("updated the component inputs and got this response: ", response);
                 }
+            } else if (this.isOutputsTabSelected) {
+                const changedOutputs: OutputBEModel[] = this.changedData.map((changedOutput) => {
+                    changedOutput = <OutputFeModel>changedOutput;
+                    const outputBE = new OutputBEModel(changedOutput);
+                    outputBE.defaultValue = changedOutput.getJSONDefaultValue();
+                    return outputBE;
+                });
+                request = this.componentServiceNg2
+                .updateComponentOutputs(this.component, changedOutputs);
+                handleSuccess = (response) => {
+                    // reset each changed property with new value and remove it from changed properties list
+                    response.forEach((resOutput) => {
+                        const changedOutput = <OutputFeModel>this.changedData.shift();
+                        this.outputsUtils.resetOutputDefaultValue(changedOutput, resOutput.defaultValue);
+                        changedOutput.required = resOutput.required;
+                        changedOutput.requiredOrig = resOutput.required;
+                    });
+                    console.log("updated the component outputs and got this response: ", response);
+                }
             }
 
             this.savingChangedData = true;
@@ -945,6 +1037,10 @@
         this.propertyInputTabs.setTabIndication('Inputs', numInputs);
     };
 
+    setOutputTabIndication = (numOutputs: number): void => {
+        this.propertyInputTabs.setTabIndication('Outputs', numOutputs);
+    };
+
     setPolicyTabIndication = (numPolicies: number): void => {
         this.propertyInputTabs.setTabIndication('Policies', numPolicies);
     }
@@ -955,6 +1051,12 @@
         this.updateHasChangedData();
     }
 
+    resetUnsavedChangesForOutput = (output: OutputFeModel) => {
+        this.outputsUtils.resetOutputDefaultValue(output, output.defaultValue);
+        this.changedData = this.changedData.filter((changedItem) => changedItem.uniqueId !== output.uniqueId);
+        this.updateHasChangedData();
+    }
+
     deleteInput = (input: InputFEModel) => {
         //reset any unsaved changes to the input before deleting it
         this.resetUnsavedChangesForInput(input);
@@ -986,6 +1088,17 @@
             }, error => {}); //ignore error
     };
 
+    deleteOutput = (output: OutputFeModel) => {
+        this.resetUnsavedChangesForOutput(output);
+        console.log("==>" + this.constructor.name + ": deleteOutput");
+        let outputToDelete = new OutputBEModel(output);
+        this.componentServiceNg2.deleteOutput(this.component, outputToDelete)
+        .subscribe(response => {
+            this.outputs = this.outputs.filter(output => output.uniqueId !== response.uniqueId);
+            this.changeSelectedInstance(this.selectedInstanceData);
+        }, error => {});
+    };
+
     deletePolicy = (policy: PolicyInstance) => {
         this.loadingPolicies = true;
         this.topologyTemplateService
@@ -1083,6 +1196,32 @@
     private isInput = (instanceType:string):boolean =>{
         return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR;
     }
-    
 
+    openOutputsValidationModal = (outputProperties: string[]) => {
+        this.translateService.languageChangedObservable.subscribe((lang) => {
+            this.alertMsgBodyTxt = this.translateService
+            .translate('OUTPUT_DECLARED',{outputProperties: outputProperties});
+            this.ModalService.openAlertModal(this.alertMsgTitle, this.alertMsgBodyTxt);
+        });
+    }
+
+    private validateOutputPropertyDeclaration = (propertiesToCreate: InstancePropertiesAPIMap):boolean =>{
+        let outputProperties: string[] = new Array();
+        if (this.outputs) {
+            this.outputs.forEach(output => {
+                _.forEach(
+                    propertiesToCreate.componentInstanceProperties[output.instanceUniqueId],
+                    (prop: PropertyBEModel) => {
+                        if (output.propertyId == prop.uniqueId) {
+                            outputProperties.push(prop.name);
+                        }
+                    });
+            });
+        }
+        if (outputProperties.length > 0) {
+            this.openOutputsValidationModal(outputProperties);
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/services/outputs.utils.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/services/outputs.utils.ts
new file mode 100644
index 0000000..27132f8
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/properties-assignment/services/outputs.utils.ts
@@ -0,0 +1,40 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 Nordix Foundation. All rights reserved.
+*  ================================================================================
+*  Licensed under the Apache License, Version 2.0 (the "License");
+*  you may not use this file except in compliance with the License.
+*  You may obtain a copy of the License at
+*
+*        http://www.apache.org/licenses/LICENSE-2.0
+*  Unless required by applicable law or agreed to in writing, software
+*  distributed under the License is distributed on an "AS IS" BASIS,
+*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*  See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+*  SPDX-License-Identifier: Apache-2.0
+*  ============LICENSE_END=========================================================
+*/
+
+import {Injectable} from '@angular/core';
+import {OutputFeModel} from "app/models";
+
+@Injectable()
+export class OutputsUtils {
+
+  constructor() {
+  }
+
+  public initDefaultValueObject = (output: OutputFeModel): void => {
+    output.resetDefaultValueObjValidation();
+    output.defaultValueObj = output.getDefaultValueObj();
+    output.updateDefaultValueObjOrig();
+  };
+
+  public resetOutputDefaultValue = (output: OutputFeModel, newDefaultValue: string): void => {
+    output.defaultValue = newDefaultValue;
+    this.initDefaultValueObject(output);
+  }
+
+}
diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts
index 760bfc5..d7ef98d 100644
--- a/catalog-ui/src/app/ng2/services/component-services/component.service.ts
+++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts
@@ -23,7 +23,7 @@
 import {Observable} from 'rxjs/Observable';
 import 'rxjs/add/operator/map';
 import 'rxjs/add/operator/toPromise';
-import { Component, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData, OperationModel, CreateOperationResponse, ArtifactModel} from "app/models";
+import { Component, InputBEModel, OutputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData, OperationModel, CreateOperationResponse, ArtifactModel} from "app/models";
 import {COMPONENT_FIELDS} from "app/utils";
 import {ComponentGenericResponse} from "../responses/component-generic-response";
 import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map";
@@ -297,7 +297,6 @@
         return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/archive', {})
     }
 
-
     deleteInput(component:Component, input:InputBEModel):Observable<InputBEModel> {
 
         return this.http.delete<InputBEModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input')
@@ -306,6 +305,13 @@
             })
     }
 
+    deleteOutput(component:Component, output:OutputBEModel):Observable<OutputBEModel> {
+        return this.http.delete<OutputBEModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + output.uniqueId + '/output')
+        .map((res) => {
+            return new OutputBEModel(res);
+        })
+    }
+
     updateComponentInputs(component:Component, inputs:InputBEModel[]):Observable<InputBEModel[]> {
 
         return this.http.post<InputBEModel[]>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', inputs)
@@ -314,6 +320,13 @@
             })
     }
 
+    updateComponentOutputs(component:Component, outputs:OutputBEModel[]):Observable<OutputBEModel[]> {
+        return this.http.post<OutputBEModel[]>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/outputs', outputs)
+        .map((res) => {
+            return res.map((output) => new OutputBEModel(output));
+        })
+    }
+
     filterComponentInstanceProperties(component:Component, filterData:FilterPropertiesAssignmentData):Observable<InstanceBePropertiesMap> {//instance-property-be-map
         let params: HttpParams = new HttpParams();
         _.forEach(filterData.selectedTypes, (type:string) => {
diff --git a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts
index a0f34ae..089cb4e 100644
--- a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts
+++ b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts
@@ -121,6 +121,11 @@
             [COMPONENT_FIELDS.COMPONENT_INPUTS, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_PROPERTIES]);
     }
 
+    getComponentOutputs(componentType: string, componentId: string): Observable<ComponentGenericResponse> {
+        return this.getComponentDataByFieldsName(componentType, componentId,
+            [COMPONENT_FIELDS.COMPONENT_OUTPUTS]);
+    }
+
     getComponentDeploymentArtifacts(component: Component): Observable<ComponentGenericResponse> {
         return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_DEPLOYMENT_ARTIFACTS]);
     }
@@ -159,6 +164,11 @@
         return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputs);
     }
 
+    createOutput(component: Component, outputsToCreate: InstancePropertiesAPIMap, isSelf: boolean): Observable<any> {
+        const outputs = isSelf ? { serviceProperties: outputsToCreate.componentInstanceProperties } : outputsToCreate;
+        return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/outputs', outputs);
+    }
+
     restoreComponent(componentType: string, componentId: string) {
         return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/restore', {});
     }
diff --git a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
index c6be9c3..5066a43 100644
--- a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
+++ b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
@@ -23,7 +23,7 @@
  */
 
 import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel,
-    InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup} from "app/models";
+    InputBEModel, OutputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup} from "app/models";
 import {CommonUtils} from "app/utils";
 import {Serializable} from "../utils/serializable";
 import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model";
@@ -45,6 +45,7 @@
     public componentInstances:Array<ComponentInstance>;
     public componentInstancesInterfaces: Map<string, Array<InterfaceModel>>;
     public inputs:Array<InputBEModel>;
+    public outputs:Array<OutputBEModel>;
     public capabilities:CapabilitiesGroup;
     public requirements:RequirementsGroup;
     public properties:Array<PropertyModel>;
@@ -82,6 +83,9 @@
         if(response.inputs) {
             this.inputs = CommonUtils.initInputs(response.inputs);
         }
+        if(response.outputs) {
+            this.outputs = CommonUtils.initOutputs(response.outputs);
+        }
         if(response.attributes) {
             this.attributes = CommonUtils.initAttributes(response.attributes);
         }
diff --git a/catalog-ui/src/app/utils/common-utils.ts b/catalog-ui/src/app/utils/common-utils.ts
index eadb92b..081c7c9 100644
--- a/catalog-ui/src/app/utils/common-utils.ts
+++ b/catalog-ui/src/app/utils/common-utils.ts
@@ -21,7 +21,7 @@
 import * as _ from "lodash";
 import {Module, AttributeModel, ResourceInstance, PropertyModel, InputFEModel, OperationModel} from "../models";
 import {ComponentInstanceFactory} from "./component-instance-factory";
-import {InputBEModel, PropertyBEModel, RelationshipModel} from "app/models";
+import {InputBEModel, OutputBEModel, PropertyBEModel, RelationshipModel} from "app/models";
 import { PolicyInstance } from "app/models/graph/zones/policy-instance";
 import { GroupInstance } from "../models/graph/zones/group-instance";
 import { InterfaceModel } from "../models/operation";
@@ -94,6 +94,17 @@
         return inputs;
     }
 
+    static initOutputs(outputsObj: Array<OutputBEModel>): Array<OutputBEModel> {
+
+        let outputs = new Array<OutputBEModel>();
+        if (outputsObj) {
+            _.forEach(outputsObj, (output: OutputBEModel): void => {
+                outputs.push(new OutputBEModel(output));
+            })
+        }
+        return outputs;
+    }
+
     static initBeProperties(propertiesObj: Array<PropertyBEModel>): Array<PropertyBEModel> {
 
         let properties = new Array<PropertyBEModel>();
diff --git a/catalog-ui/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts
index c794774..26cc920 100644
--- a/catalog-ui/src/app/utils/constants.ts
+++ b/catalog-ui/src/app/utils/constants.ts
@@ -380,6 +380,7 @@
     static COMPONENT_INSTANCES = "componentInstances";
     static COMPONENT_INSTANCES_RELATION = "componentInstancesRelations";
     static COMPONENT_INPUTS = "inputs";
+    static COMPONENT_OUTPUTS = "outputs";
     static COMPONENT_METADATA = "metadata";
     static COMPONENT_DEPLOYMENT_ARTIFACTS = "deploymentArtifacts";
     static COMPONENT_INFORMATIONAL_ARTIFACTS = "artifacts";
diff --git a/catalog-ui/src/assets/languages/en_US.json b/catalog-ui/src/assets/languages/en_US.json
index dc5fd1d..97f8c39 100644
--- a/catalog-ui/src/assets/languages/en_US.json
+++ b/catalog-ui/src/assets/languages/en_US.json
@@ -541,6 +541,12 @@
     "DELETE_POLICY_TITLE": "Delete Policy",
     "DELETE_POLICY_MSG": "Are you sure you want to delete policy '{{policyName}}'?",
 
+    "=========== PROPERTIES ASSIGNMENT DECLARE AS OUTPUT ===========": "",
+    "DELETE_OUTPUT_TITLE": "Delete Output",
+    "DELETE_OUTPUT_MSG": "Are you sure you want to delete output '{{outputName}}'?",
+    "PROPERTY_ALREADY_DECLARED": "The selected property is already declared as Output",
+    "OUTPUT_DECLARED": "Output declared: '{{outputProperties}}'",
+
     "=========== AUTOMATED UPGRADE ===========": "",
     "RESOURCE_UPGRADE_TITLE" : "Upgrade Services",
     "SERVICE_UPGRADE_TITLE" : "Update Service References",