Update embedded Swagger UI on toggle button change - html

I want to provide three different OpenApi definitions in a webapp, so users can read the documentation of different APIs.
The plan is to have a toggle button group with three buttons at the top and the swagger ui underneath it.
My problem is, that the swagger ui won't update if I click on a button. My approach looks like this:
api-docs.component.html
<mat-card>
<mat-button-toggle-group style="width: auto; display: flex;" (change)="toggleApiDoc($event)">
<mat-button-toggle checked value="mainPlattform" style="width: 100%">Main Plattform</mat-button-toggle>
<mat-button-toggle value="adapterHttp" style="width: 100%">Adapter HTTP</mat-button-toggle>
<mat-button-toggle value="adapterMqtt" style="width: 100%">Adapter MQTT</mat-button-toggle>
</mat-button-toggle-group>
<app-swagger-ui [url]=activeApiDoc></app-swagger-ui>
</mat-card>
api-docs.component.ts
import { Component } from '#angular/core';
import { MatButtonToggleChange } from '#angular/material/button-toggle';
import { environment } from 'src/environments/environment';
#Component({
selector: 'app-api-docs',
templateUrl: './api-docs.component.html',
styleUrls: ['./api-docs.component.scss']
})
export class ApiDocsComponent {
readonly mainApiDoc = environment.main_api_doc;
readonly httpAdapterApiDoc = environment.http_adapter_doc;
readonly mqttAdapterApiDoc = environment.http_adapter_doc;
activeApiDoc = this.mainApiDoc;
constructor() {
}
toggleApiDoc(event: MatButtonToggleChange) {
switch (event.value) {
case 'mainPlattform':
this.activeApiDoc = this.mainApiDoc;
break;
case 'adapterHttp':
this.activeApiDoc = this.httpAdapterApiDoc;
break;
case 'adapterMqtt':
this.activeApiDoc = this.mqttAdapterApiDoc;
break;
default:
this.activeApiDoc = this.mainApiDoc;
break;
}
}
}
swagger-ui.component.html
<div id="swagger"></div>
swagger-ui.component.ts
import { Component, Input, OnInit } from '#angular/core';
import SwaggerUI from 'swagger-ui';
#Component({
selector: 'app-swagger-ui',
templateUrl: './swagger-ui.component.html',
styleUrls: ['./swagger-ui.component.scss']
})
export class SwaggerUiComponent implements OnInit {
#Input() url: string = "";
constructor() { }
ngOnInit(): void {
const ui = SwaggerUI({
url: this.url,
dom_id: '#swagger'
});
}
}
environment.ts
export const environment = {
main_api_doc: 'https://petstore.swagger.io/v2/swagger.json',
http_adapter_doc: 'https://raw.githubusercontent.com/hjacobs/connexion-example/master/swagger.yaml'
};
As you can see I use random yaml files to test this. The first one gets rendered. I have an complete Swagger UI embedded in my webapp, but it won't render another Swagger UI, when I click a different toggle button. It just stays the same.
As you can tell, I'm not so good with typescript and angular. So I guess it shouldn't be too hard. But I can't tell whats wrong here.

The problem seems to be the angular lifecycle. When I tried to view all docs at the same time I saw that still only one would get rendered.
I changed the lifecycle hook function, where I create the Swagger UI and now it works.
import { Component, Input, OnChanges } from '#angular/core';
import SwaggerUI from 'swagger-ui';
#Component({
selector: 'app-swagger-ui',
templateUrl: './swagger-ui.component.html',
styleUrls: ['./swagger-ui.component.scss']
})
export class SwaggerUiComponent implements OnChanges {
#Input() url: string = "";
constructor() { }
ngOnChanges() {
const ui = SwaggerUI({
url: this.url,
dom_id: '#swagger'
});
}
}

Related

Angular: sanitizer.bypassSecurityTrustHtml does not render attribute (click)

I try to render a button and it works fine, but when I click the button it doesn't execute alertWindow function, help!:
app.component.ts:
import {
Component,
ElementRef,
OnInit,
ViewEncapsulation } from '#angular/core';
import { DomSanitizer, SafeHtml } from "#angular/platform-browser";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.ShadowDom,
})
export class AppComponent implements OnInit {
public content: SafeHtml;
constructor(private sanitizer: DomSanitizer) {}
async ngOnInit() { this.renderButton(); }
alertWindow() { alert("don't work"); }
renderButton() {
this.content =
this.sanitizer.bypassSecurityTrustHtml(`
<button (click)='connectWallet()' class="button">
Connect your wallet
</button>`);
}
app.component.ts;
<div [innerHTML]="content"></div>
Solution
Based on what I understand you wanted to display HTML dynamically at runtime? then solution is to use
ComponentFactoryResolver
and ViewContainerRef
It will be better if you can provide more details, what you are trying to achieve, so that people can guide you
Why it didn't work?
It doesn't work because it is outside of angular, when you use innerHTML then whatever you passed to it is pure vanilla HTML and JavaScript
Try this example
(window as any).alertWindow = function () {
alert("don't works");
};
#Component({...})
export class AppComponent {
...
renderButton() {
this.content = this.sanitizer.bypassSecurityTrustHtml(`
<button onclick='alertWindow()' class="button">Connect your wallet</button>
`);
}
}
It works right?
As you can see I have moved alrertWindow function outside of component's class and added to window variable and also changed (click) to onclick

ViewEncapsulation.None not working with innertHTML

I'm actually developing an angular application and I have to put an [innerHTML] element in a div.
My code
Like that :
something.component.html
<section class="mx-auto" *ngFor="let publication of publication">
<div [innerHTML]="publication.content"></div>
</section>
So in ts :
something.component.ts
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Subscription } from 'rxjs';
import { ActivatedRoute } from '#angular/router';
import { Title, Meta } from '#angular/platform-browser';
import { Publication } from '../publication.model';
import { PublicationsService } from '../publication.service';
#Component({
selector: 'app-free-publication',
templateUrl: './something.component.html',
styleUrls: ['./something.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FreePublicationComponent implements OnInit {
publication: Publication[] = [];
suggestions: Publication[] = [];
private routeSub: Subscription;
getId: any;
isLoading = false;
constructor(public publicationsService: PublicationsService, private route: ActivatedRoute, private titleService: Title, private meta: Meta) {
this.getId = this.route.url['_value'][1].path;
this.getId = + this.getId;
}
ngOnInit() {
this.isLoading = true;
// main publication
this.routeSub = this.route.params.subscribe(params => {
this.publicationsService.getPublication(params['publicationId']).then(dataPublication => {
for (let i = 0; (dataPublication.content.match(/wp-content/g) || []).length; i++) {
dataPublication.content = dataPublication.content.replace('https://aurelienbamde.com/wp-content/', 'assets/content/');
}
this.titleService.setTitle(dataPublication.title);
this.meta.addTag({ name: 'keywords', content: dataPublication.post_tag });
this.publication = [dataPublication];
});
});
}
}
And my innertHTML do not return the style of the html doc that I send.
My tests
With a console.log() at the end of ngOnInit, I can see my html with all of the styles attributs, but by inspecting the div of the innerHTML, there is no style inside.
My question
So I well implement ViewEncapsulation.None as you see, there is an action on other elements, so it works, but not on my innerHTML.
Do you have any idea, problem of version ? Or coworking with others elements ?
Thanks in advance for your time !
And I wish you success in your projects.
You must bypass the security imposed by angular for dangerous content (HTML content not generated by the app). There is a service, called DomSanitizer that enables you to declare a content as safe, preventing angular to filter potentially harm things to be used like styles, classes, tags etc. You basically need to pass your content through this sanitizer using a pipe:
<div [innerHTML]="dangerousContent | safeHtml"></div>
Your SafeHtmlPipe would be something like this:
#Pipe({name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value)
}
}
There are other bypassSecurityTrust* methods in DomSanitizer:
bypassSecurityTrustScript
bypassSecurityTrustStyle
bypassSecurityTrustUrl
bypassSecurityTrustResourceUrl
You can find more info in Angular docs.

How to make an HTTP request on page load in Angular (works with a button)

I am trying to make an HTTP request to an API when my page loads and have the text of the response display on the screen. I am using Angular framework.
I currently have it working as I desire when you press a button. I want the exact functionality I have with the button, but for it to happen automatically on page load.
//TypeScript
import { Component, OnInit } from '#angular/core';
import { Observable, Subscription } from 'rxjs';
import { HttpClient, HttpParams, HttpHeaders } from '#angular/common/http';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
posts: Observable<String[]>;
constructor(private http:HttpClient) {
}
public getPosts() {
this.posts = this.http.get<any[]>('https://jsonplaceholder.typicode.com/posts');
}
ngOnInit() {
}
}
HTML
<button (click) = "getPosts()">GetPosts</button>
<div *ngFor="let post of posts | async">
Name
{{post | json}}
</div>
This gives me a page with a button. When I press the button I get the information from the API. I want the API to give me the information right away when the page is loaded.
Just Invoke the method on ngOnInit
ngOnInit() {
this.getPosts()
}
or You can do like below also
export class ProfileComponent implements OnInit {
posts: Observable<String[]> = this.http.get<any[]>('https://jsonplaceholder.typicode.com/posts');
constructor(private http:HttpClient) {
}
}

Component Interaction #Input

I would like a component to send input to another component. Below is the code .ts and .html. of the two components.
Now the problem is that the html page of the parent component also shows the html part of the child component ... I want the component to pass only one string to the child component
Parent.ts
import ...
#Component({
selector: 'app-parent',
templateUrl: './parent.html',
styleUrls: ['./parent.css']
})
export class ParentComponent implements OnInit {
sostegno : string;
constructor() { }
ngOnInit() { }
avvia1() {
this.sostegno = "xxx";
this.router.navigate(['./xxx'], { relativeTo: this.route });
}
avvia2()
this.sostegno = "yyy";
this.router.navigate(['./yyy'], { relativeTo: this.route });
}
}
Parent.html
<div>
...
</div>
<app-child [sostegno]="sostegno"></app-child>
Child.ts
import ...
#Component({
selector: 'app-child',
templateUrl: './child.html',
styleUrls: ['./child.css']
})
export class ChildComponent implements OnInit {
#Input() sostegno : string;
constructor() { }
ngOnInit() {
console.log(this.sostegno);
}
}
There are some changes which you need to make because looking at the code which your currently have it seems incomplete.
You are using this.router without injecting the Router class in your constructor.
You are using this.route without injecting the ActivatedRoute class in your constructor.
To test that your parent > child interaction is working you can remove your param and instead place a test for the html
<app-child [sostegno]="'Test'"></app-child>
This should work for your ngOnInit function which is inside of your child component. If this works all you need to do now is either initialize sostegno in your parent component else your console log inside your child component will not reflect the changes when you call avvia1 or avvia2 inside of your parent class.
Hope this helps!

Open modal form containing form created from ngx-formly from another ngx-formly form

I'm currently using ngx-formly to dynamically create a bunch of Angular forms from JSON, which works really nicely. I have a peculiar use case where a custom button on a form, should open a modal dialog containing another form on click, which would also contain a form created using ngx-formly. The example I saw on the ngx-formly site use a custom button, and creates a custom component with .ts files, but I want to avoid that since I would have several forms doing this, and I don't want to create different components for this.
Is there a way to trigger a modal dialog from an ngx-formly form, to show the modal with ngx-formly form without having to create multiple components(.ts) files for them?
Common Bootstrap Model with dynamic data
Example with jQuery:
https://stackblitz.com/edit/ngx-bootstrap-fh92s3
modal.service.ts
import {Injectable} from '#angular/core';
import {ModalModel} from './modal.model';
import {Subject} from "rxjs/Subject";
declare let $: any;
#Injectable()
export class ModalService {
modalData = new Subject<ModalModel>();
modalDataEvent = this.modalData.asObservable();
open(modalData: ModalModel) {
this.modalData.next(modalData);
$('#myModal').modal('show');
}
}
modal.component.ts
import { Component } from '#angular/core';
import { ModalService } from './modal.service';
import {ModalModel} from './modal.model';
declare let $: any;
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: [ './modal.component.css' ]
})
export class ModalComponent {
modalData: ModalModel;
constructor(private modalService: ModalService) {
this.modalService.modalDataEvent.subscribe((data) => {
this.modalData = data;
})
}
}
calling this service from any component
import { Component } from '#angular/core';
import { ModalService } from '../modal/modal.service';
import { ModalModel } from '../modal/modal.model';
declare let $: any;
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: [ './home.component.css' ]
})
export class HomeComponent {
modelData = new ModalModel();
constructor(private modalService: ModalService) {
}
open() {
this.modelData.header = 'This is my dynamic HEADER from Home component';
this.modelData.body = 'This is my dynamic BODY from Home component';
this.modelData.footer = 'This is my dynamic footer from Home component';
this.modalService.open(this.modelData);
}
}
Example without jQuery i.e with ngx-bootstrap: https://stackblitz.com/edit/angular-ngx-bootstrap-modal