So I'm currently making an image that, when clicked, it expands using MatDialog. I'm currently creating a new component to make the MatDialog, however, one thing bugs me.
In my main component's HTML, I have the image as shown in the code below:
<section class="carousel" *ngIf="image">
<div class="container">
<div class="background">
<img class="image-src" [src]="image" alt="" (click)="openImage()" />
</div>
</div>
</section>
As seen, when the image is clicked, it triggers the openImage() in my typescript component:
openImage(){
this.dialog
.open(DialogExpandImageComponent)
.afterClosed()
.subscribe(() => console.log("test successful"))
}
My issue here is that, for the Dialog that expands the image, I'm creating another HTML and TS.
The TS code is currently as follows:
import { Component } from '#angular/core';
import { MatDialogRef } from '#angular/material/dialog';
#Component({
selector: 'app-dialog-expand-image',
templateUrl: './dialog-expand-image.component.html',
styleUrls: ['./dialog-expand-image.component.scss'],
})
export class DialogExpandImageComponent {
constructor(public dialogRef: MatDialogRef<DialogExpandImageComponent>) {}
closeImage() {
this.dialogRef.close();
}
}
In my HTML, I just put a "TEST" word and it works when I click the image (the TEST is displayed accordingly). My issue here is that I don't know how I'm going to bring the image that's on my other component (the main component above where the image can be clicked) to be shown in this current dialog component. If anyone has a good suggestion, I'd be bery thankful.
You need to send the data when you perform the open dialog, and catch them in the dialog component by using injection token of MAT_DIALOG_DATA
// in other component
dialog.open(YourDialog, {
data: { name: 'austin' },
});
// dialog component
import {Component, Inject} from '#angular/core';
import {MAT_DIALOG_DATA} from '#angular/material/dialog';
#Component({
selector: 'your-dialog',
template: 'passed in {{ data.name }}',
})
export class YourDialog {
constructor(#Inject(MAT_DIALOG_DATA) public data: {name: string}) { }
}
// dialog template
<ng-template let-data>
Hello, {{data.name}}
</ng-template>
For example if you have a thumbnail and you click it you will open the dialog and send the image url or image id and render it in the dialog template.
reference: https://material.angular.io/components/dialog/overview
Angular Material suggests sending the data using the injection token of MAT_DIALOG_DATA, so in the openImage() method you have some options, with your code you can pass the image as a parameter and send it as part of the data to the modal
openImage(image: string) {
this.dialog
.open(DialogExpandImageComponent, { data: { image } })
.afterClosed()
.subscribe(() => console.log('test successful'));
}
So, in your modal DialogExpandImageComponent you can receive like this
#Component({
selector: 'app-dialog-expand-image',
templateUrl: './dialog-expand-image.component.html',
styleUrls: ['./dialog-expand-image.component.scss'],
})
export class DialogExpandImageComponent {
constructor(
public dialogRef: MatDialogRef<DialogExpandImageComponent>,
#Inject(MAT_DIALOG_DATA) public data: DialogData
) {}
closeImage() {
this.dialogRef.close();
}
}
I have an example similar running, I put parts of your code, and works this is the link code:
https://stackblitz.com/edit/angular-2egim9-pqiqxc?file=src%2Fapp%2Fdialog-overview-example.ts
link Demo running: https://angular-2egim9--4200.local.webcontainer.io/
Overview:
Related
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'
});
}
}
Before clicking on the on the navbar the <ngx-datatable> looks like this
And after clicking on the navbar it looks like this
As you see in the second picture the navbar columun did not resize after clicking.
Here is the code
<ngx-datatable
#table
class="material"
[rows]="data"
[loadingIndicator]="loadingIndicator"
columnMode="force"
[headerHeight]="60"
[footerHeight]="80"
rowHeight="auto"
[limit]="10"
[scrollbarH]="scrollBarHorizontal"
[reorderable]="reorderable"
[selected]="selected"
[selectionType]="'checkbox'"
(select)="onSelect($event)"
>
I looked for help but but couldn't find the solution. Thank you.
You need to refresh the rows of the table every time you click on the side bar toggle.
In my use case, I needed to do the refresh after some amount of time along with ChangeDetectorRef to make it work.
Here is a snippet of my code:
import { ChangeDetectorRef, Component, OnInit } from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
constructor(private changeDetector: ChangeDetectorRef) { }
ngOnInit(): void {
// ...
}
// Call this function every time you close or open your sidenav
resizeTable() {
setTimeout(() => {
this.data = [...this.data];
this.changeDetector.detectChanges();
}, 500);
}
}
Hopefully this helps!
I'm a beginner to learn this component. And I going to try to create a online book shop like this link https://www.fishpond.com.hk/Books , and I'm facing some problem now. Could you guys please help me? And first in my website, it have backend and frontend, and now I can show all book , insert new book, and now I want to know how can I do when I click the title of the book and what I have to do to transfer to get that book detail.
How can I click the title and I will see those book detail on the book-details page. And I hope get the isbn code to find that book.
My code here
HTML
<h1>All Books</h1>
<ul *ngIf="books" class="info">
<li *ngFor="let book of books">
<p><img [src]="book.image" class="bookimg"></p>
<a routerLink="/book-detail"><h3>{{ book.title }}</h3></a>
<p>{{ "By "+ book.author }}</p>
<span class="price-block" >{{ "HK$" + book.price}}</span>
</li>
</ul>
ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
h1Style: boolean = false;
books: Object;
constructor(private data: DataService) {}
ngOnInit() {
this.data.getBooks().subscribe(data=> {
console.log({data}); //show data
this.books = data
//console.log(this.books);
})
}
And I have created a component for book-detail
<h1>Book-detail</h1>
<div *ngIf="books" class="book-detail-block">
<div *ngFor="let bookrecord of books" class="book-record">
<h1>{{bookrecord.title}}</h1>
<p>{{bookrecord.image}}</p>
<p>{{bookrecord.author}}</p>
<p>{{bookrecord.price}}</p>
<p>{{bookrecord.isbn}}</p>
<p>{{bookrecord.description}}</p>
</div>
</div>
ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
#Component({
selector: 'app-book-detail',
templateUrl: './book-detail.component.html',
styleUrls: ['./book-detail.component.scss']
})
export class BookDetailComponent implements OnInit {
h1Style: boolean = false;
books: Object;
constructor(private data: DataService) {}
ngOnInit() {
this.data.getOneBook().subscribe(data => {
this.books = data
console.log(this.books);
})
}
}
I can get the data in the service but how can I implement in show component
export class BookDetailComponent implements OnInit {
h1Style: boolean = false;
books: Object;
constructor(private data: DataService) {}
ngOnInit() {
console.log('-0-----' + this.books)
this.data.getBooks().subscribe(data=> {
console.log({data}); //show data
this.books = data
})
}
}
enter image description here
I may be late to the issue and you've already solved it but in the off-chance that you havent i'll hopefully provide some guidance.
What you want for accessing an individual item when clicking the title is to use a click-event on the tag representing the title, probably the h1-tag. It looks something like this:
<h1 (click)="getBookDetail(bookrecord)">{{bookrecord.title}}</h1>
The line above hooks up a clickevent to a function called getBookDetail and takes the individual object as a parameter, as of now this will render an error saying there's no function named getBookDetail(), so you'll need to create it in the component.ts file that corresponds to the view probably the homecomponent.ts and it looks like this:
getBookDetail(book: any) {
console.log(book);
}
If you now reload the application and click the title you'll see the individual book-object being logged in the console. What you'll need after is to set up routing if you havent already (you get the question to include app-routes module when creating the project) and to create a path for the bookDetailComponent. If you have routing in place add an array of routes as following:
const routes: Routes = [
{ path: '', redirectTo: '/books', pathMatch: 'full' },
{ path: 'books', component: HomeComponent},
{ path: 'book/:id', component: BookDetailComponent },
];
The first item in the routes array will match any route that is empty like localhost:4200 and redirect to the HomeComponent, and the other two arrays are routes for the individual component.
And if you dont have a routing-module i suggest you follow angulars tutorial for adding in-app navigation: https://angular.io/tutorial/toh-pt5.
And for making the click on the title actually navigate to the bookcomponent you first need to inject the Router class, so if you go back to the homecomponent you'll see an constructor (if not create one), add the router class like:
constructor(private router: Router) {}
And in the getBookDetail function you can remove the console.log and add:
getBookDetail(book: any) {
// Wrong path this.router.navigateByUrl('/book/' + book.isbn);
this.router.navigateByUrl('/bookdetail/' + book.isbn);
}
All that you need now is to get the isbn from the url and fetch one book with that identifier, but i'll leave those steps to you, everything you'll need is in the angular tutorial i linked previously. Good luck and if anything is confusing or you have any questions feel free to ask.
Added a stackblitz showing my idea:
https://stackblitz.com/edit/angular-ivy-c2znl2?file=src/app/books/books.component.ts
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
My tabs.ts (simpilified) - data used to generated tabs with *ngFor is brought from php backend:
import ...
export interface Group {
id: number;
group: string;
};
#Component( {
template: `
<ion-tabs #myTabs selectedIndex="0">
<ion-tab *ngFor="let tab of userGroups" [root]="page" [rootParams]="tab.id" [tabTitle]="tab.group" tabIcon="pulse"></ion-tab>
</ion-tabs>
`
})
export class GroupsTabsPage {
userGroups: Group[];
page: any = TabStudentsPage;
constructor( public app: App, public api: Api, public navParams: NavParams ) {
this.api.getGroupsList()
.subscribe(
data => {
this.userGroups = data;
},
err => {
this.app.getRootNav().push( LoginPage )
}
);
// ionViewDidEnter() {
// }
}
}
The result is invisible tabs. But when you hover your mouse ovet them, the cursor changes into 'hand' and you can click them. When clicked, the whole tabs bar becomes visible and all works as expected.
When I used #ViewChild to refer to the tabs elements, the interesting thing is that its 'length' property is always 0 (I checked in ionViewDidLoad event). Trying to select one of the tabs programatically also fails - they are like ghosts;)
Also when you place at least one static tab next to *ngFor ones in the template, all *ngFor ones show up but the static is always selected no matter what you select programatically or in selectedIndex property on tabs element.
Any idea guys? I've wasted three days..
that's a known bug, take a look at the element css, the subview's .tabbar has opacity of 0. I've just fixed it with a an override of opacity: 1. Ugly, but works...
Creating ion-tab from observable (dynamically) has some bugs (duplicates, wrong rendering etc) I use a workaround to avoid it, it consist of removing and loading the ion-tabs runtime every time then observable changes.
Parent template:
<div #pluginTabContainer></div>
Parent component:
#ViewChild("pluginTabContainer", {read: ViewContainerRef}) pluginTabContainer:ViewContainerRef;
...
plugins$.subscribe((pluginTabs:Array<PluginTabType>) => { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(PluginTabContainerComponent); this.pluginTabContainer.clear(); this.pluginTabContainertRef = this.pluginTabContainer.createComponent(componentFactory); this.pluginTabContainertRef.instance.data = pluginTabs;
...
ngOnDestroy() { this.pluginTabContainertRef.destroy(); }
Loaded ion-tabs template:
<ion-tabs> <ion-tab *ngFor="let tab of data" [root]="'PluginTabPage'" [rootParams]="tab"></ion-tab> </ion-tabs>
Loaded ion-tabs component (getting parameter):
#Input() data: PluginTabType;
Hope will be helpful for you.
I had a similar issue during development and I was able to solve this by making ngOninit async and calling a timeout to set the selected tab.
view
<ion-tabs #ctrlPanelTabs class="tabs-basic">
<ion-tab *ngFor="let appTab of appTabs" tabTitle={{appTab.name}} [root]="rootPage"></ion-tab>
</ion-tabs>
1) ngOninit is async
2) this.ctrlPanelTabs.select(0); is set inside a timeout function
import { Component, OnInit, ViewChild } from '#angular/core';
import { NavController, Tabs } from 'ionic-angular';
import { AppSettings } from '../../common/app.config';
import { AppTab } from '../../models/app-tab';
import { AppTabService } from '../../services/app-tab.service';
import { PanelTabComponent } from './panel-tab';
#Component({
selector: 'page-control-panel',
templateUrl: 'control-panel.html',
providers: [AppTabService]
})
export class ControlPanelPage implements OnInit {
#ViewChild("ctrlPanelTabs") ctrlPanelTabs: Tabs;
appTabs: AppTab[] = [];
message: string;
rootPage = PanelTabComponent;
constructor(public navCtrl: NavController,
private appTabService: AppTabService) {
console.log("Control Panel Page : Constructor called..");
}
async ngOnInit() {
console.log("Control Panel Page : Entering ngOninit..");
await this.loadAppTabs();
setTimeout(() => {
this.ctrlPanelTabs.select(0);
}, 100);
console.log("Control Panel Page : Exiting ngOninit..");
}
async loadAppTabs() {
console.log("Control Panel Page : Entering loadAppTabs..");
await this.appTabService.getAppTabsHierarchyBySlaveDeviceId(AppSettings.selSlaveDeviceId)
.then((response: any) => {
this.appTabs = JSON.parse(response.result);
console.log(this.appTabs);
console.log("Control Panel Page : Exiting loadAppTabs..");
});
}
}