mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-10774] move host settings to demo shell (#8051)
* move host settings to demo shell * [ci:force] empty commit
This commit is contained in:
@@ -1,5 +1,28 @@
|
||||
{
|
||||
"APP": {
|
||||
"HOST_SETTINGS": {
|
||||
"TYPE-AUTH": "Authentication type",
|
||||
"BASIC": "Basic Authentication",
|
||||
"SSO": "SSO",
|
||||
"IMPLICIT-FLOW": "Implicit Flow",
|
||||
"PROVIDER": "Provider",
|
||||
"REQUIRED": "This field is required",
|
||||
"CS_URL_ERROR": "Content Services address doesn't match the URL format",
|
||||
"PS_URL_ERROR": "Process Services address doesn't match the URL format",
|
||||
"TITLE": "Settings",
|
||||
"CS-HOST": "Content Services URL",
|
||||
"BP-HOST": "Process Services URL",
|
||||
"BACK": "Back",
|
||||
"APPLY": "APPLY",
|
||||
"NOT_VALID": "http(s)://host|ip:port(/path) not recognized, try a different URL.",
|
||||
"REDIRECT": "Redirect URI",
|
||||
"REDIRECT_LOGOUT": "Redirect URI Logout",
|
||||
"SILENT": "Silent Login",
|
||||
"SCOPE": "Scope",
|
||||
"CLIENT": "Client ID",
|
||||
"PUBLIC_URLS": "Public urls silent Login",
|
||||
"SECRET": "Secret"
|
||||
},
|
||||
"ABOUT": {
|
||||
"DEVELOPMENT":"Dev Mode"
|
||||
},
|
||||
|
@@ -0,0 +1,158 @@
|
||||
<div class="adf-setting-container">
|
||||
<mat-card class="adf-setting-card">
|
||||
<form id="host-form" [formGroup]="form" (submit)="onSubmit(form.value)" (keydown)="keyDownFunction($event)">
|
||||
|
||||
<mat-form-field *ngIf="showSelectProviders">
|
||||
<mat-select id="adf-provider-selector" placeholder="Provider" [formControl]="providersControl">
|
||||
<mat-option *ngFor="let provider of providers" [value]="provider">
|
||||
{{ provider }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="adf-authentication-type">
|
||||
<div> {{'APP.HOST_SETTINGS.TYPE-AUTH' | translate }} : </div>
|
||||
<mat-radio-group formControlName="authType" >
|
||||
<mat-radio-button value="BASIC">{{'APP.HOST_SETTINGS.BASIC' | translate }}
|
||||
</mat-radio-button>
|
||||
<mat-radio-button value="OAUTH">{{'APP.HOST_SETTINGS.SSO' | translate }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="isALL() || isECM()">
|
||||
<mat-card-content>
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{'APP.HOST_SETTINGS.CS-HOST' | translate }}</mat-label>
|
||||
<input matInput [formControl]="ecmHost" data-automation-id="ecmHost" type="text"
|
||||
id="ecmHost" placeholder="http(s)://host|ip:port(/path)">
|
||||
<mat-error *ngIf="ecmHost.hasError('pattern')">
|
||||
{{ 'APP.HOST_SETTINGS.NOT_VALID'| translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="ecmHost.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<p>
|
||||
</mat-card-content>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isALL() || isBPM()">
|
||||
<mat-card-content>
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{'APP.HOST_SETTINGS.BP-HOST' | translate }}</mat-label>
|
||||
<input matInput [formControl]="bpmHost" data-automation-id="bpmHost" type="text"
|
||||
id="bpmHost" placeholder="http(s)://host|ip:port(/path)">
|
||||
<mat-error *ngIf="bpmHost.hasError('pattern')">
|
||||
{{ 'APP.HOST_SETTINGS.NOT_VALID'| translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="bpmHost.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isOAUTH()">
|
||||
<mat-card-content>
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>Identity Host</mat-label>
|
||||
<input matInput name="identityHost" id="identityHost" formControlName="identityHost"
|
||||
placeholder="http(s)://host|ip:port(/path)">
|
||||
<mat-error *ngIf="identityHost.hasError('pattern')">
|
||||
{{ 'APP.HOST_SETTINGS.NOT_VALID'| translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="identityHost.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isOAUTH()">
|
||||
<div formGroupName="oauthConfig">
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>Auth Host</mat-label>
|
||||
<input matInput name="host" id="oauthHost" formControlName="host"
|
||||
placeholder="http(s)://host|ip:port(/path)">
|
||||
<mat-error *ngIf="host.hasError('pattern')">
|
||||
{{ 'APP.HOST_SETTINGS.NOT_VALID'| translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="host.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{ 'APP.HOST_SETTINGS.CLIENT'| translate }}</mat-label>
|
||||
<input matInput name="clientId" id="clientId" formControlName="clientId"
|
||||
placeholder="Client Id">
|
||||
<mat-error *ngIf="clientId.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{ 'APP.HOST_SETTINGS.SCOPE'| translate }}</mat-label>
|
||||
<input matInput name="{{ 'APP.HOST_SETTINGS.SCOPE'| translate }}"
|
||||
formControlName="scope" placeholder="Scope Id">
|
||||
<mat-error *ngIf="scope.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{ 'APP.HOST_SETTINGS.SECRET'| translate }}</mat-label>
|
||||
<input matInput name="{{ 'APP.HOST_SETTINGS.SECRET'| translate }}"
|
||||
formControlName="secret" placeholder="{{ 'APP.HOST_SETTINGS.SECRET'| translate }}">
|
||||
<mat-error *ngIf="secret.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<label for="silentLogin">{{ 'APP.HOST_SETTINGS.SILENT'| translate }}</label>
|
||||
<mat-slide-toggle class="adf-full-width" name="silentLogin" [color]="'primary'"
|
||||
formControlName="silentLogin">
|
||||
</mat-slide-toggle>
|
||||
|
||||
<label for="implicitFlow">{{ 'APP.HOST_SETTINGS.IMPLICIT-FLOW'| translate }}</label>
|
||||
<mat-slide-toggle class="adf-full-width" name="implicitFlow" [color]="'primary'"
|
||||
formControlName="implicitFlow">
|
||||
</mat-slide-toggle>
|
||||
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{ 'APP.HOST_SETTINGS.REDIRECT'| translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'APP.HOST_SETTINGS.REDIRECT'| translate }}"
|
||||
name="redirectUri" formControlName="redirectUri">
|
||||
<mat-error *ngIf="redirectUri.hasError('required')">
|
||||
{{ 'APP.HOST_SETTINGS.REQUIRED'| translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{ 'APP.HOST_SETTINGS.REDIRECT_LOGOUT'| translate }}</mat-label>
|
||||
<input id="logout-url" matInput placeholder="{{ 'APP.HOST_SETTINGS.REDIRECT_LOGOUT'| translate }}"
|
||||
name="redirectUriLogout" formControlName="redirectUriLogout">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="adf-full-width">
|
||||
<mat-label>{{ 'APP.HOST_SETTINGS.PUBLIC_URLS'| translate }}</mat-label>
|
||||
<input id="public-url" matInput placeholder="{{ 'APP.HOST_SETTINGS.PUBLIC_URLS'| translate }}"
|
||||
name="publicUrls" formControlName="publicUrls">
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
<mat-card-actions align="end">
|
||||
<button mat-button (click)="onCancel()" color="primary">
|
||||
{{'APP.HOST_SETTINGS.BACK' | translate }}
|
||||
</button>
|
||||
<button type="submit" id="host-button" class="adf-login-button" mat-raised-button
|
||||
color="primary" data-automation-id="host-button"
|
||||
[disabled]="!form.valid">
|
||||
{{'APP.HOST_SETTINGS.APPLY' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
@@ -0,0 +1,22 @@
|
||||
.adf-host-settings {
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
align-items: center;
|
||||
|
||||
.adf-authentication-type {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.adf-setting-container {
|
||||
width: 800px;
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.adf-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -0,0 +1,287 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Output, ViewEncapsulation, OnInit, Input } from '@angular/core';
|
||||
import { Validators, UntypedFormGroup, UntypedFormBuilder, UntypedFormControl } from '@angular/forms';
|
||||
import { AppConfigService, AppConfigValues, StorageService, AlfrescoApiService, OauthConfigModel } from '@alfresco/adf-core';
|
||||
import { ENTER } from '@angular/cdk/keycodes';
|
||||
|
||||
export const HOST_REGEX = '^(http|https):\/\/.*[^/]$';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-host-settings',
|
||||
templateUrl: 'host-settings.component.html',
|
||||
host: {
|
||||
class: 'adf-host-settings'
|
||||
},
|
||||
styleUrls: ['./host-settings.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class HostSettingsComponent implements OnInit {
|
||||
/**
|
||||
* Tells the component which provider options are available. Possible valid values
|
||||
* are "ECM" (Content), "BPM" (Process) , "ALL" (Content and Process), 'OAUTH2' SSO.
|
||||
*/
|
||||
@Input()
|
||||
providers: string[] = ['BPM', 'ECM', 'ALL'];
|
||||
|
||||
showSelectProviders = true;
|
||||
|
||||
form: UntypedFormGroup;
|
||||
|
||||
/** Emitted when the URL is invalid. */
|
||||
@Output()
|
||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||
error = new EventEmitter<string>();
|
||||
|
||||
/** Emitted when the user cancels the changes. */
|
||||
@Output()
|
||||
cancel = new EventEmitter<boolean>();
|
||||
|
||||
/** Emitted when the changes are successfully applied. */
|
||||
@Output()
|
||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||
success = new EventEmitter<boolean>();
|
||||
|
||||
constructor(private formBuilder: UntypedFormBuilder,
|
||||
private storageService: StorageService,
|
||||
private alfrescoApiService: AlfrescoApiService,
|
||||
private appConfig: AppConfigService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.providers.length === 1) {
|
||||
this.showSelectProviders = false;
|
||||
}
|
||||
|
||||
const providerSelected = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
|
||||
|
||||
const authType = this.appConfig.get<string>(AppConfigValues.AUTHTYPE, 'BASIC');
|
||||
|
||||
this.form = this.formBuilder.group({
|
||||
providersControl: [providerSelected, Validators.required],
|
||||
authType
|
||||
});
|
||||
|
||||
this.addFormGroups();
|
||||
|
||||
if (authType === 'OAUTH') {
|
||||
this.addOAuthFormGroup();
|
||||
this.addIdentityHostFormControl();
|
||||
}
|
||||
|
||||
this.form.get('authType').valueChanges.subscribe((value) => {
|
||||
if (value === 'BASIC') {
|
||||
this.form.removeControl('oauthConfig');
|
||||
this.form.removeControl('identityHost');
|
||||
} else {
|
||||
this.addOAuthFormGroup();
|
||||
this.addIdentityHostFormControl();
|
||||
}
|
||||
});
|
||||
|
||||
this.providersControl.valueChanges.subscribe(() => {
|
||||
this.removeFormGroups();
|
||||
this.addFormGroups();
|
||||
});
|
||||
}
|
||||
|
||||
private removeFormGroups() {
|
||||
this.form.removeControl('bpmHost');
|
||||
this.form.removeControl('ecmHost');
|
||||
}
|
||||
|
||||
private addFormGroups() {
|
||||
this.addBPMFormControl();
|
||||
this.addECMFormControl();
|
||||
}
|
||||
|
||||
private addOAuthFormGroup() {
|
||||
const oauthFormGroup = this.createOAuthFormGroup();
|
||||
this.form.addControl('oauthConfig', oauthFormGroup);
|
||||
}
|
||||
|
||||
private addBPMFormControl() {
|
||||
if ((this.isBPM() || this.isALL() || this.isOAUTH()) && !this.bpmHost) {
|
||||
const bpmFormControl = this.createBPMFormControl();
|
||||
this.form.addControl('bpmHost', bpmFormControl);
|
||||
}
|
||||
}
|
||||
|
||||
private addIdentityHostFormControl() {
|
||||
const identityHostFormControl = this.createIdentityFormControl();
|
||||
this.form.addControl('identityHost', identityHostFormControl);
|
||||
}
|
||||
|
||||
private addECMFormControl() {
|
||||
if ((this.isECM() || this.isALL()) && !this.ecmHost) {
|
||||
const ecmFormControl = this.createECMFormControl();
|
||||
this.form.addControl('ecmHost', ecmFormControl);
|
||||
}
|
||||
}
|
||||
|
||||
private createOAuthFormGroup(): UntypedFormGroup {
|
||||
const oauth = this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, {} as any);
|
||||
|
||||
return this.formBuilder.group({
|
||||
host: [oauth.host, [Validators.required, Validators.pattern(HOST_REGEX)]],
|
||||
clientId: [oauth.clientId, Validators.required],
|
||||
redirectUri: [oauth.redirectUri, Validators.required],
|
||||
redirectUriLogout: [oauth.redirectUriLogout],
|
||||
scope: [oauth.scope, Validators.required],
|
||||
secret: oauth.secret,
|
||||
silentLogin: oauth.silentLogin,
|
||||
implicitFlow: oauth.implicitFlow,
|
||||
publicUrls: [oauth.publicUrls]
|
||||
});
|
||||
}
|
||||
|
||||
private createBPMFormControl(): UntypedFormControl {
|
||||
return new UntypedFormControl(this.appConfig.get<string>(AppConfigValues.BPMHOST), [Validators.required, Validators.pattern(HOST_REGEX)]);
|
||||
}
|
||||
|
||||
private createIdentityFormControl(): UntypedFormControl {
|
||||
return new UntypedFormControl(this.appConfig.get<string>(AppConfigValues.IDENTITY_HOST), [Validators.required, Validators.pattern(HOST_REGEX)]);
|
||||
}
|
||||
|
||||
private createECMFormControl(): UntypedFormControl {
|
||||
return new UntypedFormControl(this.appConfig.get<string>(AppConfigValues.ECMHOST), [Validators.required, Validators.pattern(HOST_REGEX)]);
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.cancel.emit(true);
|
||||
}
|
||||
|
||||
onSubmit(values: any) {
|
||||
this.storageService.setItem(AppConfigValues.PROVIDERS, values.providersControl);
|
||||
|
||||
if (this.isBPM()) {
|
||||
this.saveBPMValues(values);
|
||||
} else if (this.isECM()) {
|
||||
this.saveECMValues(values);
|
||||
} else if (this.isALL()) {
|
||||
this.saveECMValues(values);
|
||||
this.saveBPMValues(values);
|
||||
}
|
||||
|
||||
if (this.isOAUTH()) {
|
||||
this.saveOAuthValues(values);
|
||||
}
|
||||
|
||||
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
|
||||
|
||||
this.alfrescoApiService.reset();
|
||||
this.alfrescoApiService.getInstance().invalidateSession();
|
||||
this.success.emit(true);
|
||||
}
|
||||
|
||||
keyDownFunction(event: any) {
|
||||
if (event.keyCode === ENTER && this.form.valid) {
|
||||
this.onSubmit(this.form.value);
|
||||
}
|
||||
}
|
||||
|
||||
private saveOAuthValues(values: any) {
|
||||
if (values.oauthConfig.publicUrls && (typeof values.oauthConfig.publicUrls === 'string')) {
|
||||
values.oauthConfig.publicUrls = values.oauthConfig.publicUrls.split(',');
|
||||
}
|
||||
|
||||
this.storageService.setItem(AppConfigValues.OAUTHCONFIG, JSON.stringify(values.oauthConfig));
|
||||
this.storageService.setItem(AppConfigValues.IDENTITY_HOST, values.identityHost);
|
||||
}
|
||||
|
||||
private saveBPMValues(values: any) {
|
||||
this.storageService.setItem(AppConfigValues.BPMHOST, values.bpmHost);
|
||||
}
|
||||
|
||||
private saveECMValues(values: any) {
|
||||
this.storageService.setItem(AppConfigValues.ECMHOST, values.ecmHost);
|
||||
}
|
||||
|
||||
isBPM(): boolean {
|
||||
return this.providersControl.value === 'BPM';
|
||||
}
|
||||
|
||||
isECM(): boolean {
|
||||
return this.providersControl.value === 'ECM';
|
||||
}
|
||||
|
||||
isALL(): boolean {
|
||||
return this.providersControl.value === 'ALL';
|
||||
}
|
||||
|
||||
isOAUTH(): boolean {
|
||||
return this.form.get('authType').value === 'OAUTH';
|
||||
}
|
||||
|
||||
get providersControl(): UntypedFormControl {
|
||||
return this.form.get('providersControl') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get bpmHost(): UntypedFormControl {
|
||||
return this.form.get('bpmHost') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get ecmHost(): UntypedFormControl {
|
||||
return this.form.get('ecmHost') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get host(): UntypedFormControl {
|
||||
return this.oauthConfig.get('host') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get identityHost(): UntypedFormControl {
|
||||
return this.form.get('identityHost') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get clientId(): UntypedFormControl {
|
||||
return this.oauthConfig.get('clientId') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get scope(): UntypedFormControl {
|
||||
return this.oauthConfig.get('scope') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get secret(): UntypedFormControl {
|
||||
return this.oauthConfig.get('secret') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get implicitFlow(): UntypedFormControl {
|
||||
return this.oauthConfig.get('implicitFlow') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get silentLogin(): UntypedFormControl {
|
||||
return this.oauthConfig.get('silentLogin') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get redirectUri(): UntypedFormControl {
|
||||
return this.oauthConfig.get('redirectUri') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get publicUrls(): UntypedFormControl {
|
||||
return this.oauthConfig.get('publicUrls') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get redirectUriLogout(): UntypedFormControl {
|
||||
return this.oauthConfig.get('redirectUriLogout') as UntypedFormControl;
|
||||
}
|
||||
|
||||
get oauthConfig(): UntypedFormControl {
|
||||
return this.form.get('oauthConfig') as UntypedFormControl;
|
||||
}
|
||||
|
||||
}
|
@@ -1,2 +1 @@
|
||||
This component is only for internal use
|
||||
<adf-host-settings (cancel)="onCancel()" (success)="onSuccess()" (error)="onError($event)"></adf-host-settings>
|
||||
|
@@ -20,6 +20,8 @@ import { SettingsComponent } from './settings.component';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HostSettingsComponent } from './host-settings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -32,8 +34,13 @@ const routes: Routes = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes),
|
||||
CoreModule
|
||||
CoreModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
declarations: [SettingsComponent]
|
||||
declarations: [
|
||||
SettingsComponent,
|
||||
HostSettingsComponent
|
||||
]
|
||||
})
|
||||
export class AppSettingsModule {}
|
||||
|
Reference in New Issue
Block a user