Angular component with dynamic template created using ɵcompileComponent (Angular 9) is not working in production mode - angular9

I am creating a dynamic component in Angular 9. I have a <ul> tag in the html template, the sub-elements of which are dynamically loaded from the server (The server will return values like <li>One</li><li>Two</li><li (click)="onLinkClicked(3)">Three</li> using handlebar templates).
private createComponentFromRaw(template: string, containerRef: ElementRef) {
class DynamicComponent {
onLinkClicked(resource: any) {
console.log(resource);
}
}
ɵcompileComponent(DynamicComponent, { template, changeDetection: ChangeDetectionStrategy.OnPush });
ɵrenderComponent(DynamicComponent, {
host: containerRef.nativeElement,
injector: this.injector,
hostFeatures: [ɵLifecycleHooksFeature],
});
}
On calling this.createComponentFromRaw('<li>One</li>', this.ref.element); the component is rendered as expected when run as ng serve but throws the following error at runtime in production mode (ng build --prod):
ERROR Error: Angular JIT compilation failed: '#angular/compiler' not loaded!
- JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
- Did you bootstrap using '#angular/platform-browser-dynamic' or '#angular/platform-server'?
- Alternatively provide the compiler with 'import "#angular/compiler";' before bootstrapping.

Looks like the only option currently available is to set buildOptimizer: false in the angular.json
This happens because the buildOptimizer incorrectly considers #angular/compiler as having no side-effects and removes it as part of the tree shaking

Related

Typescript return HTML Template Element from Constructor typing not working

It is 'illegal' to use new when creating an instance of Template, where Template extends an HTMLTemplateElement.
To overcome this limitation, I get and return an HTMLTemplateElement using document.getElementById(id) from the Template constructor as below:
export class Template {
private htmlTemplateElement: HTMLTemplateElement;
constructor(id: string) {
this.htmlTemplateElement = document.getElementById(id) as HTMLTemplateElement;
return Object.assign(this.htmlTemplateElement, this)
}
public test = () => this.htmlTemplateElement.innerHTML
}
Providing an HTML Template Element exist in the DOM,
I can create a new instance of Template and use the extension method test() as illustrated below:
const template = new Template(id)
console.log(template.test())
console.log(template.innerHTML)
Both console.log() works just fine and prints the correct text to the console.
HOWEVER, the typescript compiler complains about template.innerHTML.
The error I get, saying innerHTML does not exist on type Template
Question: How can I add type information so I do not get a compiler error?
I have tried to use export class Template extends HTMLTemplateElement.
That does not work since it is illegal to create an instance using new.
I love typescript, but sometimes the type checking gets in my way.
Help me out here, please.
Although not ideal, I was able to keep the typescript compiler happy by implementing the following interface:
export interface ITemplate {
[key:string]: any;
test(): string;
}
and then using the interface:
export Template implements ITemplate {
...
}
Note:
Why did I not use customElements.define(<tag-name>,Template)?
I do not intend to create a new custom instance of HTMLTemplateElement, I just want to return an existing HTMLTemplateElement with additional utility extension methods.
Also, It might very well be possible that my approach is completely wrong.
However, that is a different topic than the question asked here.

Custom error classes not extending correctly [duplicate]

I'm trying to throw a custom error with my "CustomError" class name printed in the console instead of "Error", with no success:
class CustomError extends Error {
constructor(message: string) {
super(`Lorem "${message}" ipsum dolor.`);
this.name = 'CustomError';
}
}
throw new CustomError('foo');
The output is Uncaught Error: Lorem "foo" ipsum dolor.
What I expect: Uncaught CustomError: Lorem "foo" ipsum dolor.
I wonder if that can be done using TS only (without messing with JS prototypes)?
Are you using typescript version 2.1, and transpiling to ES5? Check this section of the breaking changes page for possible issues and workaround: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
The relevant bit:
As a recommendation, you can manually adjust the prototype immediately after any super(...) calls.
class FooError extends Error {
constructor(m: string) {
super(m);
// Set the prototype explicitly.
Object.setPrototypeOf(this, FooError.prototype);
}
sayHello() {
return "hello " + this.message;
}
}
However, any subclass of FooError will have to manually set the prototype as well. For runtimes that don't support Object.setPrototypeOf, you may instead be able to use __proto__.
Unfortunately, these workarounds will not work on Internet Explorer 10 and prior. One can manually copy methods from the prototype onto the instance itself (i.e. FooError.prototype onto this), but the prototype chain itself cannot be fixed.
The problem is that Javascript's built-in class Error breaks the prototype chain by switching the object to be constructed (i.e. this) to a new, different object, when you call super and that new object doesn't have the expected prototype chain, i.e. it's an instance of Error not of CustomError.
This problem can be elegantly solved using 'new.target', which is supported since Typescript 2.2, see here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
class CustomError extends Error {
constructor(message?: string) {
// 'Error' breaks prototype chain here
super(message);
// restore prototype chain
const actualProto = new.target.prototype;
if (Object.setPrototypeOf) { Object.setPrototypeOf(this, actualProto); }
else { this.__proto__ = actualProto; }
}
}
Using new.target has the advantage that you don't have to hardcode the prototype, like some other answers here proposed. That again has the advantage that classes inheriting from CustomError will automatically also get the correct prototype chain.
If you were to hardcode the prototype (e.g. Object.setPrototype(this, CustomError.prototype)), CustomError itself would have a working prototype chain, but any classes inheriting from CustomError would be broken, e.g. instances of a class VeryCustomError < CustomError would not be instanceof VeryCustomError as expected, but only instanceof CustomError.
See also: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
As of TypeScript 2.2 it can be done via new.target.prototype.
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example
class CustomError extends Error {
constructor(message?: string) {
super(message); // 'Error' breaks prototype chain here
this.name = 'CustomError';
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
}
It works correctly in ES2015 (https://jsfiddle.net/x40n2gyr/). Most likely, the problem is that the TypeScript compiler is transpiling to ES5, and Error cannot be correctly subclassed using only ES5 features; it can only be correctly subclassed using ES2015 and above features (class or, more obscurely, Reflect.construct). This is because when you call Error as a function (rather than via new or, in ES2015, super or Reflect.construct), it ignores this and creates a new Error.
You'll probably have to live with the imperfect output until you can target ES2015 or higher...
I literally never post on SO, but my team is working on a TypeScript project, and we needed to create many custom error classes, while also targeting es5. It would have been incredibly tedious to do the suggested fix in every single error class. But we found that we were able to have a downstream effect on all subsequent error classes by creating a main custom error class, and having the rest of our errors extend that class. Inside of that main error class we did the following to have that downstream effect of updating the prototype:
class MainErrorClass extends Error {
constructor() {
super()
Object.setPrototypeOf(this, new.target.prototype)
}
}
class SomeNewError extends MainErrorClass {}
...
Using new.target.prototype was the key to getting all of the inheriting error classes to be updated without needing to update the constructor of each one.
Just hoping this saves someone else a headache in the future!
I ran into the same problem in my typescript project a few days ago. To make it work, I use the implementation from MDN using only vanilla js. So your error would look something like the following:
function CustomError(message) {
this.name = 'CustomError';
this.message = message || 'Default Message';
this.stack = (new Error()).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;
throw new CustomError('foo');
It doesn't seem to work in SO code snippet, but it does in the chrome console and in my typescript project:
I was having this problem in a nodejs server. what worked for me was to transpile down to es2017 in which these issues seems to be fixed.
Edit tsconfig to
"target": "es2017"
Try this...
class CustomError extends Error {
constructor(message: string) {
super(`Lorem "${message}" ipsum dolor.`)
}
get name() { return this.constructor.name }
}
throw new CustomError('foo')

How to pass an object to child component while creating it in HTML template markup?

I'm doing the following (it's working as expected) in my parent component.
<app-textbox [info]="{caption:'Boink',value:'Oink'}"
... ></app-textbox>
In the receiving child component I have the following declaration.
#Input() info: any;
Now I want to improve the code and make it hard-typed, so I introduced and imported the following class.
export class TextBoxInfo { constructor(public caption: string, public value: string) { } }
Then, I updated the child component's input as follows.
#Input() info: TextBoxInfo;
Everything still works, as expected but I also wanted to improve the markup in HTML by switching to the following syntax.
<app-textbox [info]="new TextBoxInfo('Boink','Oink')"
... ></app-textbox>
That doesn't work and I'm getting the error message .
Uncaught Error: Template parse errors:
Parser Error: Unexpected token 'TextBoxInfo' at column 5 in [new TextBoxInfo('Boink', 'Oink')]
in ng:///AppModule/ParentComponent.html#45:24 ("
/div>
app-textbox [ERROR ->][info]="new TextBoxInfo('Boink', 'Oink')" ...
I've try to google to confirm or contradict that I can use the syntax like new Something(...) in the template's markup. Nothing conclusive this far. I also tried to google for the error but it's simply telling me that the syntax isn't recognized. I haven't found any viable examples of how to create an object and pass it in the template and googlearching it is complicated by the lack of good key words.
Am I approaching the object creation incorrectly?
Using type literals in templates is not supported. The scope of a template is the component instance, and therefore only properties of the component instance can be accessed.
If you need to reference identifiers outside of that scope, you need to move the code/expression to the components class and expose it to the template from there.
class MyComponent {
createTextBoxInfo(p1, p2):TextBoxInfo { return new TextBoxInfo(p1, p2); }
}
[info]="createTextBoxInfo('Boink','Oink')"
while this concrete case is a bad example in practice.
It would create a new TextBoxInfo every time change detection is run which is probably not what you want and will bring the performance of your app to its knees.
It's better to assign the value to a property and bind to that instead:
class MyComponent {
textBoxInfo = new TextBoxInfo('Boink','Oink'); }
}
[info]="textBoxInfo"

How can I configure webpack + aurelia to use server-side templates

I'm trying to get my app to use server-side templates(.cshtml) and have tried the recommended approach of overriding the ViewLocator.prototype.convertOriginToViewUrl.
Unfortunately it doesn't seem to work with webpack, as hinted by #EisenbergEffect. on my previous question
I have a route that returns html and I want to use that as my view.
Example here in ASP.NET MVC
public ActionResult Template(string view)
{
return View(string.Format("~/Views/Shared/ClientTemplates/{0}.cshtml", view));
}
Aurelia code
import {ViewLocator} from 'aurelia-framework';
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
ViewLocator.prototype.convertOriginToViewUrl = (origin) => {
return "/common/template?view=" + origin.moduleId;
};
aurelia.start().then(a => a.setRoot());
}
Result Error: Cannot find module './/common/template?view=app'.
Running the route /common/template?view=app in a browser returns, as expected, the markup that resides in the app.cshtml
How can I configure webpack + aurelia to use server-side templates?

Babel error: Class constructor Foo cannot be invoked without 'new'

I am using babel to transpile.
I have class BaseComponent which is extended by class Logger.
When I run new Logger() in the browser, I am getting this error
Class constructor BaseComponent cannot be invoked without 'new'
the code that throws this is:
var Logger = function (_BaseComponent) {
_inherits(Logger, _BaseComponent);
function Logger() {
_classCallCheck(this, Logger);
return _possibleConstructorReturn(this, Object.getPrototypeOf(Logger).call(this, "n")); //throws here
}
Due to the way ES6 classes work, you cannot extend a native class with a transpiled class. If your platform supports native classes, my recommendation would be, instead of using the preset es2015, use es2015-node5, assuming you're on Node 5. That will cause Babel to skip compiling of classes so that your code uses native classes, and native classes can extend other native classes.
Another solution is to include { exclude: ["transform-es2015-classes"] } in .babelrc
presets: [
["env", { exclude: ["transform-es2015-classes"] }]
]
UPDATE: In the latest version of "env" preset plugin names have changed (e.g. it's now "transform-classes"). Use the "debug" option to inspect which plugins are included.