Angular 7 animation trigger causes console error - javascript

I am trying to set up a fadeInOut animation on a component.
My app module imports BrowserAnimationsModule.
I created an animation and a trigger in a separate file:
import { animate, style, animation, trigger, useAnimation, transition } from '#angular/animations';
export const fadeIn = animation([style({ opacity: 0 }), animate('500ms', style({ opacity: 1 }))]);
export const fadeOut = animation(animate('500ms', style({ opacity: 0 })));
export const fadeInOut = trigger('fadeInOut', [
transition('void => *', useAnimation(fadeIn)),
transition('* => void', useAnimation(fadeOut))
]);
Then, I created a component and verified that the component itself works:
import { Component, OnInit } from '#angular/core';
import { Globals } from '#app/globals';
import { fadeInOut } from '#app/animations';
#Component({
selector: 'app-global-alert',
template: `
<div class="global-alert" *ngIf="globalAlert">
<div class="global-alert-message"><ng-content></ng-content></div>
<div class="close" (click)="closeGlobalAlert()"></div>
</div>
`,
styles: [],
animations: [fadeInOut]
})
export class GlobalAlertComponent implements OnInit {
private globalAlert: boolean;
constructor(private globals: Globals) {
this.globalAlert = globals.hasGlobalAlert;
}
ngOnInit() {}
closeGlobalAlert() {
this.globals.hasGlobalAlert = false;
this.globalAlert = false;
}
}
Note that I am storing the state of whether this alert should appear in a globals.ts file, although that's unrelated:
import { Injectable } from '#angular/core';
#Injectable()
export class Globals {
hasGlobalAlert = true;
}
So I use the component inside another component's html like so:
<div>
lots of html
</div>
<app-global-alert>Hello world</app-global-alert>
This works, the alert is dismissed when you click the close button, everything works as expected. However, when I try to add my trigger to it
<app-global-alert [#fadeInOut]>Hello world</app-global-alert>
I get a console error
Error: Found the synthetic property #fadeInOut. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.
I've Googled this, but AFAICT I've covered all the gotchas in most of the replies: I've included the animations declaration in the component, etc.
What did I miss?

As stated in official docs, animation should be added to component's metadata property. There's such property in GlobalAlertComponent here:
#Component({
animations: [fadeInOut],
...
})
export class GlobalAlertComponent implements OnInit {
...
This allows to use animation on any element inside of html part of this component. But #fadeInOut animation has been used in another component's html here:
<div>
lots of html
</div>
<app-global-alert [#fadeInOut]>Hello world</app-global-alert>
Make sure this component has import and animation property in its metadata.

I get a console error Error: Found the synthetic property #fadeInOut. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.
This error occurs when you have not imported "BrowserAnimationsModule" or "NoopAnimationsModule" module in your component containing Module. If your animation component is in App module then check if app.module is having the following in the #NgModule -
#NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule //or NoopAnimationsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

You need to add BrowserAnimationsModule
In app-module.ts file in import section.
import: [
BrowserAnimationsModule
]
Hope it will help.
Happy coding :)

Related

property binding error when pipes is used

i am following this tutorial
https://ultimatecourses.com/blog/angular-pipes-custom-pipes
and i am trying to get the code posted below working. when i invoke the command
ng servr --open
i get the following error:
Error occurs in the template of component Appcomponent
ptoperty file does not exist on type Appcomponent
please let me know why i am getting this error and how to fix it
app.component.ts:
import { Component } from '#angular/core'
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
file = { name: 'logo.svg', size: 2120109, type: 'image/svg' };
}
app.component.html:
{{file.name}}
{{file.size | filesize}}
you have spell mistake in import
import { Component } from '#angulat/core'
change to
import { Component } from '#angular/core'
For using pipe we need to pass it in provider
I have created stackblitz demo you can check, it is working fine in my case I have attached here so you can check.
let me know if you still face any issue will help you.
https://stackblitz.com/edit/angular-9bk71w?file=src%2Fapp%2Fapp.module.ts
The error says
property file does not exist on type Appcomponent
Note its AppComponent... You have declared FileComponent
export class FileComponent {
file = { name: 'logo.svg', size: 2120109, type: 'image/svg' };
}
Simply you are using property file from FileComponent in Appcomponent

How to add custom directive to Angular Material Dialog component

I've created a custom directive that adds an additional #Output() event to Ionic's ion-range element. It works well on normal pages, but when I try and use it within Angular Material's Dialog component, the custom event isn't firing for some reason. My custom directives are added to a Directives module, and I typically import this Directives module where I need to use it. Here's how my project is set up:
range-events.directive.ts
#Directive({
// tslint:disable-next-line:directive-selector
selector: 'ion-range'
})
export class RangeEventsDirective {
#Output() public ionStart: EventEmitter<RangeValue> = new EventEmitter();
public constructor(protected elemRef: ElementRef<IonRange>) {}
#HostListener('mousedown', ['$event'])
#HostListener('touchstart', ['$event'])
public onStart(ev: Event): void {
this.ionStart.emit(this.elemRef.nativeElement.value);
ev.preventDefault();
}
}
This directive is declared and exported here:
directives.module.ts
#NgModule({
declarations: [
...
RangeEventsDirective,
...
],
imports: [
CommonModule
],
exports: [
...
RangeEventsDirective,
...
]
})
export class DirectivesModule { }
I've defined a custom pop up component, that shows on hover, with an edit button. When this edit button is clicked, it creates my Dialog component.
Here's my popup component:
edit-kit-popup.component.ts
#Component({
selector: 'app-edit-kit-popup',
templateUrl: './edit-kit-popup.component.html',
styleUrls: ['./edit-kit-popup.component.scss'],
})
export class EditKitPopupComponent implements OnInit {
constructor(
private dialog: MatDialog,
) { }
ngOnInit() {}
edit() {
const modalRef = this.dialog.open(EditKitSectionModalComponent, {
width: '320px',
height: '476px',
position: {
top: '20px',
right: '20px'
},
data: {
...
}
});
}
}
As you can see I use a custom component to display the dialog, that is defined in: edit-kit-section-modal.component.ts.
This dialog has an ion-range element with the #Output() event, I've added in `edit-kit-section-modal.component.html:
<ion-range #sectionHeightRange (ionStart)="customRangeStart($event)"></ion-range>
Both of these components are defined and exported in the following modules file. I then import my DirectivesModule here so that I can use the directives in the components:
press-kit-components.module.ts
#NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
RouterModule,
MaterialModule,
PipesModule,
IonicModule,
...
DirectivesModule
],
declarations: [
...
EditKitItemPopupComponent,
EditKitSectionModalComponent
],
exports: [
...
EditKitItemPopupComponent,
EditKitSectionModalComponent,
]
})
export class PressKitComponentsModule { }
Since I am using a custom component, I make sure to add the EditKitSectionModalComponent as an entryComponent in my page where the popup is used.
Is the reason why it is not registering, because the Dialog isn't present on the page at page load? So when I trigger the Dialog, the directive isn't applied?
How should I be using custom directives with an Angular Material Dialog component?
Thanks!
Interestingly enough, when I changed my selector from:
#Directive({
// tslint:disable-next-line:directive-selector
selector: 'ion-range'
})
to:
#Directive({
// tslint:disable-next-line:directive-selector
selector: '[ion-range-events]'
})
and added the directive like this:
<ion-range ion-range-events #sectionHeightRange (ionStart)="customRangeStart($event)"></ion-range>
It worked...anyone know why I can't call by the HTML selector like I did in my original question? Because calling by selector ion-range worked on non-dialog components. Thanks!

Dynamically inject HTML and CSS on request

So I have been looking around on how to load CSS and HTML from the server.
What I want to achieve is to request a certain template to be displayed which sends the HTML and CSS to the website and loads it in together with some user-defined styles like colour
So far I was able to inject HTML using:
<div [innerHTML]="template | sanitizeHtml"></div>
and
import { Pipe, PipeTransform, SecurityContext } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({
name: 'sanitizeHtml'
})
export class SanitizeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { }
transform(value: any): any {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
Which I have seen from different posts and blogs (thank you for that).
The HTML I have been building works like a charm:
this.template = "<div class='template' style='width: 1080px; height: 1920px; background-color: #212121;'><div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit;'><div class='clr-col-5'><div style='width: 230px; height: 60px; background-image: url(*LINK_TO_IMAGE*); background-repeat: no-repeat; float: left;'></div></div></div></div>"
This HTML is a part of the complete template.
So what I would like to do is to use styles on this by using variables.
So what I have tried is to make a style object:
public style: {};
public template: string;
ngOnInit(){
this.style = {
template: {
"color": "#D8B088",
}
}
this.template = "<div [ngStyle]='style.template' class='template' style='width: 1080px; height: 1920px; background-color: #212121;'><div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit;'><div class='clr-col-5'><div style='width: 230px; height: 60px; background-image: url(*LINK_TO_IMAGE*); background-repeat: no-repeat; float: left;'></div></div></div></div>"
}
I have added the style object to the template by using [ngStyle]='style.template', for some reason the style didn't get loaded, so I tried to use camelCasing instead but still no success.
So does someone know how to get the CSS to work in this case, and eventually use user-defined styles?
Thanks in advance.
Edit 1:
I have also included the Sanitize pipe in the app.module.ts:
#NgModule({
declarations: [
...,
SanitizeHtmlPipe
],
...
});
(for those who were wondering)
Edit 2:
So I have been working out what I kinda want to have with these templates:
A user can register multiple devices of where they want to display the bookings from office 365. A user can setup templates in 2 ways, but this does not matter. When a user wants to display the template for a certain device they go to /device/:deviceid/template/:templateid.
This way the component will load in the template of that device.
So first we load in the device settings which contains the user styles for the template. Afterwards, we load in the data from office365 that has to be displayed in the template and finally load in the template with the template styles.
So there will be 3 requests to the server.
DeviceSettings -- Data Office365 -- Template
So far I have been able to load in the data and place this in the template, but the template was available locally and not from the server.
The reason why I want to have the templates to be requested from the server is that there will be an admin portal where those templates will be made and managed.
These templates will have a name, the HTML and the CSS.
For big template differences you can use Angular CDK Portal: https://material.angular.io/cdk/portal/overview
Example here: https://stackblitz.com/angular/mkvvyvgqxox?file=src%2Fapp%2Fcdk-portal-overview-example.ts
Instead of using [ngStyle] in sanitized HTML, I would instead just change class for dom element, into which sanitized HTML is inserted:
<div [ngClass]="templateClass" [innerHTML]="templateHtml"></div>
In this way code is more readable and styling code is separated from HTML.
Css for templates would look like this:
.template-class-1 {
background-color: #f44336;
}
.template-class-2 {
background-color: #4caf50;
}
Update 14/10/2020:
The previous solution required the compiler to be included that way you couldn't build the project in production mode. Thanks to Owen Kelvins answer it is now possible to add dynamic html and css while still being to build to production since it doesn't require the compiler:
Angular multiple templates in one component based on id (with template store)
For adding custom CSS you can either use Owen Kelvins method or append the "" tag at the end of the html and add in your custom CSS together with the end tag.
Original Answer:
I have found the solution to this subject. Thanks to someone in the discord server "The Coding Den", he messaged me about this and give me a link to Dynamically load template for a component on Github. After scrolling through this long post I found the answer of Alarm9k. This is how I used it to create a component that could display different templates based on a given id through a server request, I have also added some comments to explain it.
import { Component, AfterViewInit, Compiler, NgModule, ViewChild, ViewContainerRef, OnInit } from '#angular/core';
import { CommonModule } from '#angular/common';
import { BookingService } from 'src/app/services/booking.service';
import { ApplicationModel } from 'src/app/models/application.model';
import { Booking } from 'src/app/models/vo/booking';
import { Subscription } from 'rxjs';
import { SplitStringPipe } from '../../utils/split-string.pipe';
import { HttpClientModule } from '#angular/common/http';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { BrowserModule } from '#angular/platform-browser';
#Component({
selector: 'app-bookings-template',
templateUrl: './bookings-template.component.html',
styleUrls: ['./bookings-template.component.css']
})
export class BookingsTemplateComponent implements AfterViewInit {
public template: string;
public date: Date;
public locale: string;
public id: string;
#ViewChild('container', { read: ViewContainerRef, static: false }) container: ViewContainerRef;
constructor(private compiler: Compiler, private bs: BookingService, private apm: ApplicationModel) { }
ngAfterViewInit() {
// Must clear cache.
this.compiler.clearCache();
// fill in template from server request
this.template = "<div class="test">{{test}}</div>;
var styles = ".test{color:red}";
// Define the component using Component decorator.
const component = Component({
template: this.template + "<div>Hard Coded html for error checks and loading spinner</div>",
styles: [styles]
})(class implements OnInit {
//example properties
public date: Date;
public bookings: Array<Booking>;
public isLoading: boolean = true;
public hasError: boolean = false;
public errorMessage: string;
public errorMessageSub: Subscription;
public bs: BookingService;
public apm: ApplicationModel;
// Do not pass any parameters in the constructor or it will break!
// Instead pass it within the factory method down below as a property!
constructor() {
// refresh template every minute
setInterval(() => {
this.ngOnInit();
}, 60000);
// refresh date every second
setInterval(() => {
this.date = new Date();
}, 1000);
}
ngOnInit() {
// get data to fill in template
}
ngOnDestroy() {
//remove error subscription
this.errorMessageSub.unsubscribe();
}
});
// Define the module using NgModule decorator.
//Modules can be changed based on your needs
const module = NgModule({
imports: [
CommonModule,
BrowserAnimationsModule,
BrowserModule,
HttpClientModule],
declarations: [component, SplitStringPipe],
providers: [BookingService]
})(class { });
// Asynchronously (recommended) compile the module and the component.
this.compiler.compileModuleAndAllComponentsAsync(module)
.then(factories => {
// Get the component factory.
const componentFactory = factories.componentFactories[0];
// Create the component and add to the view.
const componentRef = this.container.createComponent(componentFactory);
// pass parameters that would go in the constructor as properties
// subscriptions should also work.
componentRef.instance.bs = this.bs;
componentRef.instance.apm = this.apm;
componentRef.instance.errorMessageSub = this.apm.getMessageError().subscribe(me => componentRef.instance.errorMessage = me);
});
}
}
The BookingsTemplateComponent acts as the parent of the anonymous component class which acts as the child. This way the child can be added to the parent thanks to #ViewChild where the container name is specified and matches with the parent html id:
<div #container></div> (in this case).
You will also need to add some things to the app module:
import { NgModule, CompilerFactory, Compiler, COMPILER_OPTIONS } from '#angular/core';
import { JitCompilerFactory } from '#angular/platform-browser-dynamic';
import { CommonModule } from '#angular/common';
export function createCompiler(compilerFactory: CompilerFactory) {
return compilerFactory.createCompiler();
}
#NgModule({
declarations: [
// components and pipes
...
],
imports: [
CommonModule, // required
... //other modules
],
providers: [
// different services
...,
// these are need to add the compiler manually to the project
{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
{ provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
{ provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
],
bootstrap: [AppComponent]
})
export class AppModule { }
WARNING:
The most important factor of this is that you cannot build the project in production mode. The reason for this is because JIT compilation doesn't work and you will get the following error:
This is because the angular compiler is not included in the production environment, even when you try to add it manually.

ngOnInit and MediaChange testing with Karma

I am trying to test the code in the ngOnInit method. The code watches for change in screen size for a navigation bar to resize down to mobile or to stay as a top bar. I have tried a for about a week and keep getting a slew of different errors when I test. I have left out some code for comp.component.ts as the other code is not necessary for this. I keep getting subscribe is not a method or Can't resolve all parameters for MediaChange: (?, ?, ?, ?). Any advice on how I can achieve writing a test for this or any resources you might suggest looking at to help me figure this out.
comp.component.ts
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs';
import { MediaChange, ObservableMedia } from '#angular/flex-layout';
#Component({
selector: 'app-comp',
templateUrl: './comp.component.html',
styleUrls: ['./comp.component.scss']
})
export class NavigationComponent implements OnInit {
isOpen: Boolean;
watcher: Subscription;
activeMediaQuery = "";
media: ObservableMedia;
constructor() {
this.isOpen = false;
}
ngOnInit(): void {
this.watcher = this.media.subscribe((change: MediaChange) => {
this.activeMediaQuery = change ? `'${change.mqAlias}' = (${change.mediaQuery})` : '';
this.isOpen = false;
});
}
navPressed(event, path): void {
this.navClick.emit(path);
if ( this.checkSize() ) this.toggle();
}
checkSize(): Boolean {
return this.activeMediaQuery.includes('xs') || this.activeMediaQuery.includes('sm');
}
}
comp.component.spec.ts
import { Component } from '#angular/core';
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { DebugElement } from '#angular/core';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { MatButtonModule, MatToolbarModule, MatIconModule } from '#angular/material';
import { CompComponent } from './comp.component';
import { Subscription } from 'rxjs';
import { MediaChange, ObservableMedia } from '#angular/flex-layout';
#Component({
selector: 'app-test-component-wrapper',
template: '<app-navigation [navItems]="clickables" (navClick)="handleNavClick($event)"></app-navigation>'
})
class TestWrapperComponent {
clickables = [
{ path: '/login', label: 'Login', onClick() {} }
];
}
describe('app testing', () => {
let component: CompComponent;
let fixture: ComponentFixture<TestWrapperComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatButtonModule,
MatToolbarModule,
MatIconModule,
BrowserAnimationsModule
],
declarations: [
TestWrapperComponent,
NavigationComponent
],
providers: [
ObservableMedia,
MediaChange,
Subscription
]
}).compileComponents();
fixture = TestBed.createComponent(TestWrapperComponent);
}));
it('should create and have Login label', () => {
// EDIT START
spyOn(ObservableMedia, 'prototype');
// EDIT END
expect(fixture).toBeTruthy();
fixture.detectChanges();
fixture.whenStable().then(() => {
component = fixture.debugElement.children[0].componentInstance;
expect(component.navItems[0].label).toBe('Login');
});
});
});
EDIT: Added the 'EDIT' comment in the code with the code I have added. I am now getting the resolve all parameters for MediaChange: (?, ?, ?, ?) error which I think is forward progress from the subscribe error mentioned above.
Some observations:
ObservableMedia from flex-layout needs to be injected into your component to work. Details here
You aren't providing MediaChange or Subscription in the original component, so no need to in the TestBed either.
In the stackblitz below I had to make a few assumptions. Let me know if any of these are wrong, or just go ahead and update the stackblitz:
In your spec you imported CompComponent, but in comp.component.ts you defined NavigationComponent. Of the two I chose to use NavigationComponent.
navClick was missing from your code above, so I assumed it is an #Output from your component (since you emit a path to it).
navItems was also missing from the code above, but since you are testing it I assumed it was important and guessed it is an input to your component (again, just by the way you were using it).
You didn't include your template, so I mocked it very simply.
toggle was called from within navPressed, but didn't exist so I created it as an empty function.
Here is the stackblitz: https://stackblitz.com/edit/stackoverflow-q-53024049?file=app%2Fmy.component.spec.ts
To fix what you had: I made the changes above and mocked the ObservableMedia object passed in with the following:
let mockFlex = jasmine.createSpyObj({
subscribe: ({mqAlias: 'xs', mediaQuery: ''}),
isActive: true,
});
I also changed the providers array to the following:
providers: [
{ provide: ObservableMedia, useValue: mockFlex }
]
Check the stackblitz for all the details. As you can see there, the test now passes.

Angular2 custom ErrorHandler, why can I log to console but not to template?

I would like to have custom errors in my Angular2 app. Thus I have extended ErrorHandler in my component:
import { Component, ErrorHandler, OnInit } from '#angular/core';
import { GenericError } from './generic-error.component';
#Component({
selector: 'custom-error-handler',
templateUrl: 'app/error-handler/custom-error-handler.component.html?' + +new Date()
})
export class CustomErrorHandler extends ErrorHandler {
errorText: string;
constructor() {
super(false);
}
ngOnInit() {
this.errorText = 'Initial text!';
}
public handleError(error: any): void {
if (error.originalError instanceof GenericError) {
console.info('This is printed to console!');
this.errorText = "I want it to print this in the template!";
}
else {
super.handleError(error);
}
}
}
My template simply contains:
<span style="color:red">{{errorText}}</span>
First I see "Initial text!" in the template as set in ngOnInit. That's as expected.
I can then throw a new exception like this from a different component:
throw new GenericError();
and it hits the code with handleError and prints to console but it doesn't update my template errorText with:
"I want it to print this in the template!"
It's like it ignores my template, when inside the handleError function.
What could be the problem here?
* ADDED MORE INFORMATION *
I thought I should add some more information. So here is the module I made for CustomErrorHandler (maybe the problem is with the providers?):
import { NgModule, ErrorHandler } from '#angular/core';
import { CommonModule } from '#angular/common';
import { CustomErrorHandler } from './custom-error-handler.component';
#NgModule({
declarations: [
CustomErrorHandler
],
imports: [
CommonModule
],
exports: [
CustomErrorHandler
],
providers: [
{ provide: ErrorHandler, useClass: CustomErrorHandler }
]
})
export class CustomErrorModule { }
There is indeed only one instance of the CustomErrorHandler (I checked with the Augury Chrome plugin).
For completeness, here is is the GenericError component:
export class GenericError {
toString() {
return "Here is a generic error message";
}
}
The solution was to add a service as suggested in the question's comment track. This way I can set the property in the component and eventually show it in the template.
I created the service, so that it has a function which takes one parameter. Injected the service, call the service's function from the handleError in the component function, and send the text I want in the template as the parameter. Then I use an observable, to get the text back to the component.
In the constructor of the component, I added this observer.
let whatever = this.cs.nameChange.subscribe((value) => {
setTimeout(() => this.errorText = value);
});
I needed to add the setTimeout, or else it would not update the template before the second time the observable was changed.
Phew! The Angular team should make this global exception handling easier in future releases.

Categories