I need to put a loading in multiple components of my project. So instead of putting the same HTML over and over again across the components, I need to know what's the best way to not repeat code, but I don't know if this is correct thing to do. I have created a component called loading-graphic, which I bind it in every HTML file of the respective components. I read about ngTemplateOutlet and ngContent, but to be honest it doesn't make sense in my head to use it for this case (and I don't get it too... I'm a beginner on it). So, on what should I bet? Thanks.
Base on your question, I think creating Reusable Components with NgTemplateOutlet would be the best solution to avoid repeating HTML in different component Templates. It allows you to pass parameters base on your host component and makes your Angular app easier to test and develop since it sllows easily modified reusable component for various use cases without having to modify individual components itself.
Since you are a begginer I am going to Illustrate simple way of using NgTemplateOutlet, however dive deep later on Templates and Stamps.
Imaging you have a reusable Search component where you want to hide a check box base on the parent component. Your Template will look like below.
we pass data from the parent component to the child/Search component using #Input and property binding, so we define which checkboxes to hide base on Parent component.
here is the code sample for Search Component
search.component.ts
======================
import { Component, OnInit, Output, EventEmitter, Input } from '#angular/core';
#Component({
selector: 'app-search',
templateUrl: './app-search.component.html',
styleUrls: ['./app-search.component.css']
})
export class AppSearchComponent implements OnInit {
accountName: string = '';
#Output() accountSearchChange = new EventEmitter<string>(); //1. Event Binding to pass data from Child to Parent Component ->Int
#Input() searchMode: 'account' | 'holder' | 'distribution' = 'account'; //Use NgTemplateOutlet for reusable componenet
constructor() { }
ngOnInit() {
}
//2. Event Binding to pass data from Child to Parent Component ->Invoke Emit
doSearchFilter(searchText: string) {
console.log('Search Child: doSearchFilter -> ' + searchText);
this.accountSearchChange.emit(searchText);
}
clearFilters() {
console.log('Account Search: Clear Filter is called');
this.accountName = '';
}
}
search.component.html
=====================
<ng-container [ngSwitch]="searchMode">
<div class="input-full-width" *ngSwitchCase="'account'">
<mat-checkbox class="example-container check-full-width">Show active and pending only</mat-checkbox>
</div>
<div class="input-full-width" *ngSwitchCase="'holder'">
<mat-checkbox class="example-container check-full-width">View only holders with missing requirements</mat-checkbox>
</div>
<div class="input-full-width" *ngSwitchCase="'holder'">
<mat-checkbox class="example-container check-full-width">View only active Holders</mat-checkbox>
</div>
</ng-container>
I am using Search component inside Account component and below is the code sample.
in HTML file i am referring app-search css selector and pass the search Mode defined in ts.
import { Component, OnInit, ViewChild, AfterViewInit } from '#angular/core';
import { MatSort, MatTableDataSource, MatPaginator } from '#angular/material';
import { Router } from "#angular/router";
import { Observable } from 'rxjs';
import { AccountService } from 'src/app/core/services/account.service';
import { Deal } from 'src/app/core/models/deal';
#Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.css']
})
export class AccountsComponent implements OnInit, AfterViewInit {
displayedColumns: string[] = ['accountId', 'accountShortName', 'accountType'];
public dealDataSource = new MatTableDataSource<Deal>();
dealsObservable: Observable<Deal[]>;
searchMode = 'distribution';
isLoadingResults = true;
#ViewChild(MatSort) sort: MatSort;
#ViewChild(MatPaginator) paginator: MatPaginator;
constructor(private router: Router, private api: AccountService) { }
......................................................
<app-search (accountSearchChange)='doFilter($event)' [searchMode]="searchMode"></app-search>
Hope this is clear.
i think loading component is not a bad Idee. Use loading component in your app-root component. You can use a loading.service and interceptor to display or hide the component. You will get automatically a Loading Indicator for each API call.
sample: https://medium.com/#zeljkoradic/loader-bar-on-every-http-request-in-angular-6-60d8572a21a9
Related
I am trying to add something to the constructor of one of my Angular components, however, anytime I put something in the constructor, it renders the entire page blank - getting rid of all of the other components and displaying just the background.
For example - this will work.
TS
#Component({
selector: 'app-cardboxes',
templateUrl: './cardboxes.component.html',
styleUrls: ['./cardboxes.component.scss']
})
export class CardboxesComponent implements OnInit {
constructor() { }
ngOnInit(): void {}
}
And this will render completely blank - removing everything, even the other components.
TS
#Component({
selector: 'app-cardboxes',
templateUrl: './cardboxes.component.html',
styleUrls: ['./cardboxes.component.scss']
})
export class CardboxesComponent implements OnInit {
constructor(private dialog: MatDialog) { }
ngOnInit(): void {}
}
For the record, it doesn't matter what is put in the constructor - it is the same result every time. The Chrome terminal says that there is a NullInjectorError - No provider for MatDialog
Is there a reason for this, or an easy solution? I do not understand why this is happening and I really need to be able to use the constructors. Do I have to make another import somewhere? Is there a configuration I am missing?
You are trying to use angular materials. Please use this comand in console.
npm i #angular/material
Open app.module and
NgModule ({....
imports: [...,
MatSliderModule,
…]
Items adsed in constructor should be imported in module
Scenario: I have a list of companies that each have an array of projects as one of their variables. I will display the list of companies in the parent component/html, and only when clicking on their corresponding 'open' does a child component open to display the list of projects for that company. This list is a FormArray that is editable.
I created this FormArray example as the standalone projects component to interact and perform CRUD operations with example data.
My goal now is to open the form as a child component when I click the 'open' button on each individual company as in THIS stackblitz.
In the example it appears that this.setData(); within the constructor is causing the upset.
I have found through experimentation that, by commenting this line out causes the app not to crash, but of course the FormArray will not be loaded when I click the company 'open' button. However, I have also found that writing {{company.name}} in the child component DOES output the company details in the child, so it shows data is going through correctly.
I just cannot understand what is going wrong?
Try ngDoCheck() lifecyle hook
A lifecycle hook that invokes a custom change-detection function for a
directive, in addition to the check performed by the default
change-detector.
ngDoCheck() {
this.setData();
}
The problem lies in this.setData() call inside SubForm class constructor.
You never checked for null/undefined data. So, initially when the component loads, this.company is undefined. Hence, name variable will be assigned undefined value.
So, when program tries to access the value of this.company in the below line, it gives error:
setData() {
let control = <FormArray>this.myForm.controls.data;
control.push(this.fb.group({
name: this.company.name,
...
}));
}
Solution:
Add null/undefined check for this.company before calling this.setData():
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
data: this.fb.array([])
})
if(!isNullOrUndefined(this.company)){
this.setData();
}
console.log(this.company)
}
As I tried on the StackBlitz, few changes to be done to get it working:
You have to access the #Input variable in ngOnInit() by implementing an OnInit interface.
Parent Component .TS file:
isOpened : boolean = false; // one local variable of type boolean to open child compo
openInfo(company) {
this.isOpened = !this.isOpened;
this.openCompany = company;
this.open=true;
}
Parent Component HTML Code:
<mat-card *ngFor="let data of DATA">
{{data.name}}
<button mat-raised-button (click)="openInfo(data)">open</button>
</mat-card>
<div *ngIf="isOpened">
<subform [company]="openCompany"></subform>
</div>
Child Component .TS Code:
Import this:
import {
Component, OnInit, Input
} from '#angular/core';
and Component class:
export class SubForm implements OnInit {
#Input() company: any;
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
data: this.fb.array([])
})
console.log(this.company);
this.setData();
}
}
A working example
I am trying to pass data from parent component to child components, but I am getting data is undefined,
In my below code
parent component
here part_data I have declared in service
this._PartService.GetDataValues(this.search.item, this.search.filter_type, release_part).subscribe(
result => {
this.resultData = result;
this._PartService.part_data = this.resultData.data
})
child component
Here I am using observer
this.searchService.observer.subscribe(search => {
this.part = search.item;
this.specification = this._PartService.part_data.partMasterDataCompleteList[0];
})
Here I am getting PartMasterDataCompleteList[0] is undefined
Use this as the child component:-
#Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {
#Input() hero: Hero;
constructor() {}
ngOnInit() {}
}
When you need to feed data to the component, use the below code:-
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
The hero property is #Input which receives data from the outer environment.
For more information, refer to Angular tutorial:-
https://angular.io/tutorial/toh-pt3
You can pass your data in sub component and get using #Input method.
There is one other way to pass data from one component to other component.
Click here
My problem is the following, I get as a response from a service an entire HTML page that I should display back t the user. Now this is an issue for Angular since it thinks I might be a victim of cross site scripting if I do that. The source I get the HTML from is trusted so I wanted to white list it or bypass the sanitizer in some way and render the view to the user.
The problem I ran into is that the file I get also contains 'style' and 'script' tags for manipulating the dom, and no matter how I place the bypass function calls something gets caught and the entire thing doesn't render properly. Is there any way I could maybe separate the HTML file clear it and then put it back together or something else?
Try this:
import { DomSanitizer } from '#angular/platform-browser';
constructor(private sanitizer: DomSanitizer) { }
//where you want to use the unsafe html
const sanitizedHtml = this.sanitizer.bypassSecurityTrustResourceUrl(html);
What you can do, but goes against the Angular principle, is to append the html markup to the innerhtml of your component or the DOM using ElementRef from
#angular/core.
Sample appcomponent:
import { Component, ElementRef } from '#angular/core';
#Component({
selector: 'my-app',
template: ``,
})
export class AppComponent {
private htmlTemplate = `
<div>Loading template</div>
<script type="text/javascript">
console.log('loaded');
</script>
`;
constructor(private elementRef: ElementRef) { }
ngAfterViewInit() {
let elem: Element = this.elementRef.nativeElement;
elem.innerHTML = this.htmlTemplate;
}
}
Styles like
<div [style.background-image]="\'url(\' + image + \')\'">Background</div>
<div [style.transform]="rotate(7deg)"
are not added anymore
update (2.0.0 final)
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({name: 'safeHtml'})
export class SafeHtml implements PipeTransform {
constructor(private sanitizer:DomSanitizer){}
transform(html) {
return this.sanitizer.bypassSecurityTrustStyle(html);
// return this.sanitizer.bypassSecurityTrustHtml(html);
// return this.sanitizer.bypassSecurityTrustScript(html);
// return this.sanitizer.bypassSecurityTrustUrl(html);
// return this.sanitizer.bypassSecurityTrustResourceUrl(html);
}
}
See also https://angular.io/api/platform-browser/DomSanitizer
<div [innerHTML]="someHtml | safeHtml"
update
DomSanitizationService is going to be renamed to DomSanitizer in RC.6
original
This should be fixed in RC.2
See also Angular2 Developer Guide - Security
Angular2 intruduced sanitization of CSS values and property binding like [innerHTML]=... and [src]="..." in RC.1
See also https://github.com/angular/angular/issues/8491#issuecomment-217467582
The values can be marked as trusted by using DomSanitizer.bypassSecurityTrustStyle(...)
import {DomSanitizer} from '#angular/platform-browser';
...
constructor(sanitizer: DomSanitizationService) {
this.backgroundImageStyle = sanitizer.bypassSecurityTrustStyle('url(' + this.image + ')');
// for HTML
// this.backgroundImageStyle = sanitizer.bypassSecurityTrustHtml(...);
}
and binding to this value instead the untrusted plain string.
This can also be wrapped in a pipe like
#Pipe({name: 'safeStyle'})
export class Safe {
constructor(private sanitizer:Sanitizer){}
transform(style) {
return this.sanitizer.bypassSecurityTrustStyle(style);
// return this.sanitizer.bypassSecurityTrustHtml(style);
// return this.sanitizer.bypassSecurityTrustScript(value);
// return this.sanitizer.bypassSecurityTrustUrl(value);
// return this.sanitizer.bypassSecurityTrustResourceUrl(value);
}
}
<div [ngStyle]="someStyle | safeStyle"></div>
with
someHtml = `click to see the awesome`;
is still working though :-[ (it's work in progress)
Plunker example (Angular 2.0.0-rc-1)
See also Angular 2 Security Tracking Issue
and https://angular.io/docs/ts/latest/api/platform-browser/index/DomSanitizer-class.html
Hint about {{...}}
Sanitized content can't be bound using prop="{{sanitizedContent}}" because {{}} stringyfies the value before it is assigned which breaks sanitization.
Bypassing sanitizer to trust any content can be a security concern. Since Angular is not a dedicated sanitizing library, it is overzealous towards suspicious content to not take any risks. It removes almost all attributes, for example. You can delegate sanitizing to a dedicated library — DOMPurify. Here's a wrapper library I've made to easily use DOMPurify with Angular.
https://github.com/TinkoffCreditSystems/ng-dompurify
It also has a pipe to declaratively sanitize HTML:
<div [innerHtml]="value | dompurify"></div>
One thing to keep in mind is DOMPurify is great for sanitizing HTML/SVG, but not CSS. So you can provider Angular's CSS sanitizer to handle CSS:
import {NgModule, ɵ_sanitizeStyle} from '#angular/core';
import {SANITIZE_STYLE} from '#tinkoff/ng-dompurify';
#NgModule({
// ...
providers: [
{
provide: SANITIZE_STYLE,
useValue: ɵ_sanitizeStyle,
},
],
// ...
})
export class AppModule {}
It's internal — hense ɵ prefix, but this is how Angular team use it across their own packages as well anyway.