is using array.inclues in templare a performance issue in angular - html

my component have changeDetection:  ChangeDetectionStrategy.OnPush and component have a array variable
myids:Array<number>;
addId(id:number){
this.myids.push(id)
}
in the template I am using:
<div [class.active]="myids.includes(step.id)"></div>
My question, is it a performance problem to use myids.includes in template ?

Yes it is an performance issue. Everytime the template must be rerendered, this array iteration must be run, also. It's a good choice to run with changeDetectionStragy.OnPush, which minimizes the amount of template rerendering.
Good practice:
You should seperate html templates which are responsible for viewing and the code of your component which are responsible to react of events and bring the model to the view.
The core concept of that is Model-View-Controller. (MVC)
I recommend to calculate your "active" property in the controller or a service.
This property is calculated only one time and can easily bound to your template.
Do it like this:
public class MyComponent {
public isActive: boolean;
private myIds: string[];
constructor() {
this.isActive = false;
this.myIds = [];
}
public ngOnInit(): void {
// load myIds
// this.myIds = this._myIdsService.getIds();
this.isActive = this._myids.includes(step.id);
}
}
And then bind this property to your view...
<div [class.active]="isActive"></div>

Related

Overriding previous observable

I have an events app which has an all-events component which lists all the created events. When the all-events component is loaded, the events are retrieved from my database.
This is the relevant code from the all-events Typescript file:
eventParams: EventParams;
getAllEventsObs: Observable<PaginatedResult<BeachCleanEvent[]>>;
ngOnInit() {
this.eventParams = new EventParams();
}
getEvents() {
this.getAllEventsObs = this.eventService.getAllEvents(this.eventParams);
console.log("getting events");
}
pageChanged(event: any) {
this.eventParams.pageNumber = event.page;
this.eventService.setEventParams(this.eventParams);
this.getEvents();
}
Here is the html from the all-events component:
<div class="background-img">
</div>
<div class="container">
<div class="events-container" *ngIf="getAllEventsObs | async as getAllEventsObs">
<div class="event-item" *ngFor="let event of getAllEventsObs.result">
<app-event-card [existingEvent]="event"></app-event-card>
</div>
<pagination *ngIf="getAllEventsObs.pagination as eventPagination"
[boundaryLinks]="true"
[totalItems]="eventPagination.totalItems"
[itemsPerPage]="eventPagination.itemsPerPage"
[(ngModel)]="eventPagination.currentPage"
(pageChanged)="pageChanged($event)">
</pagination>
</div>
</div>
The getAllEvents method is just a simple http request. I won't show this code as I know it works correctly.
This is the PaginatedResult class:
export interface Pagination {
currentPage: number;
itemsPerPage: number;
totalItems: number;
totalPages: number;
}
export class PaginatedResult<T> {
result: T;
pagination: Pagination;
}
This is the EventParams class:
export class EventParams {
pageNumber = 1;
pageSize = 4;
}
When the page initially loads, it loads correctly and displays the first four events:
The issue I am having is that when clicking on the next page, it gets stuck and won't load the next four events. No error is displayed on the console but the "getting events" console.log I created in the getEvents method above just keeps firing:
I suspect this is something to do with the way I am consuming the observable in the html code (using the async pipe). How would I go about resolving this? Is this where I should be using the switchMap RXJS operator? If so, how would I use it in this scenario?
You are on the right track... and yes, you should use switchMap :-)
Instead of re-assigning your source observable inside getEvents(), you could simply define it to depend on your params, and just push new params when they change. switchMap will execute the service method each time the params change.
But if the EventService is keeping track of the params anyway, it's probably the simplest to have it expose the events$ based on those params.
If you aren't already, define the params as a BehaviorSubject with default value and add a method for consumers to modify them. Then expose a single events$ observable that represents the events based on the specific params:
service:
private params$ = new BehaviorSubject(new EventParams());
public setEventParams(params) {
this.params$.next(params);
}
public events$ = this.params$.pipe(
switchMap(params => this.getAllEvents(params))
);
component:
events$ = this.eventService.events$;
pageChanged(params) {
// build proper params object if necessary
this.eventService.setEventParams(params);
}
Your component code becomes a lot simpler since it doesn't need to be concerned with managing the observable and keeping track of params.

Angular 9 - Cannot find a differ supporting object 'getData()

I am getting this error trying to bind my control to its data. Here is some relevant code.
Template.
<tree-control [nodes]="getData"></tree-control>
Component.
public getData(): Observable<Array<any>> {
const assets: any = this.service.get('url', headers);
return assets;
}
Anything I have found so far is not helping. Any idea what's wrong with my code?
Thanks
First of all, you assign a function (getData) to the nodes property. I assume you want to assign the data from getData to it instead.
Secondly, the call to this.service.get is probably not being executed. Reason for that is that you do not subscribe to, what I assume, is a http-call that returns an Observable.
To fix this, you can do the following:
export class Foo {
nodeData: Observable<any>;
constructor(
private readonly service: YourService,
) {
this.nodeData = this._getData();
}
private _getData() {
return this.service.get(...);
}
}
Inside your template you can then subscribe and unsubscribe to the data automatically by using the async pipe.
<tree-control [nodes]="nodeData | async"></tree-control>
For all that to work I assume your service.get method returns an Observable.

How do I call a function/execute code automatically in Angular?

I have code in a function that I need for initialization of other variables. However, this function doesn't get called unless i call it through another tag in html. Is there any way that i can initialize this function or write the code in a way in which the code gets executed automatically as soon as the project starts executing the the website loads?
You should have a look at lifecycle hooks that are used in Angular, here is the link to the documents related.
lifecycle hooks
In here you can read about the OnInit() lifecycle hook which is triggered when a component is loaded ( after constructor ) and is an ideal place to look at initialising variables / calling functions.
public ngOnInit(): void {
this.exampleText = 'Hello Component';
}
just make sure to implement it on your class like so
export class youClassHere implements OnInit {
public exampleText: string;
public ngOnInit(): void {
//executing logic on component load
this.exampleText = 'Hello Component';
}
}
You can implement OnInit event and do it there. Take a look here OnInit. Check here if you want to now more about Lifecycle Hooks. Alternative option is to use constructor. But that's executed on class initialization.
class MyComponent implements OnInit {
ngOnInit() {
// ...
}
}
You can implement OnInit lifecycle in your class and call your function inside OnInit so that it gets called whenever your component gets mounted.

angular 2 RC4 create component

So here is the problem, I am attempting to create a new component from within a service that is injected within the App Component. I need the new component to be placed within the app component html tag not outside. The thing is I really do not want the app component to have to provide anything to the service I may need to inject the service into other places and hence not have it tightly coupled to the app component. So far I have created a DIV at the end of the app component html and then used #ViewChild to read the ViewContainerRef from this element located within the app component. This is then provided to the service via a function call so that it can make use of createComponent. This allows for the NEW component to be placed within the scope of the app component, not within the body. Unfortunately this is too dependant on the app component providing the ViewContainerRef. Any ideas of how I can create the new component as described.
Code Example
app.component.html
<app-component>
<div #newCompHook></div>
</app-component>
app.component.ts
#ViewChild('newCompHook', {read: ViewContainerRef}) newCompViewRef: ViewContainerRef;
constructor(appService: AppService) {
appService.setViewRef(this.newCompViewRef);
}
app.service.ts
private myViewRef;
constructor(private compiler: ComponentResolver){
this.myViewRef = null;
}
public setViewRef(vr: ViewContainerRef): void {
this.myViewRef = vr; // <-- DO NOT WANT TO DO THIS !!!
}
public createNewComp(childCmp: Type): void {
if (this.myViewRef !== null){
this.compiler.resolveComponent( childCmp ).then((compFactory:ComponentFactory) => this.myViewRef.createComponent(compFactory) )
}
}
createNewComp is called by an external source and may or may not provide the childCmp type to be resolved.
So any ideas of how I can do this without needing to provide anything from the app component ???
If you need to have the viewContainerRef in your service that is the only solution...
But it is not a good practice to generate HCI components in a service. It's the role of other components.
Let's take an exemple : your server send you a list of objects (a list of strings for exemple) and you want to generate a button for each string.
In your service you just manage the string list :
#Injectable()
export class MyService {
private myModel : Array<String> = new Array();
public get MyModel () : Array<String> {
return this.myModel;
}
/// Assume you have method in the service to populate the model...
}
Then it's your component which generate the HCI :
export class AppComponent {
/// Use dependency injection to get service :
constructor (private _myService : MyService){}
public get MyModel () : Array<String> {
return this.myService.MyModel;
}
}
Finally in your component template :
<div>
<ul>
<li *ngFor="let s of MyModel">
<!-- Button with your model text -->
<button>s</button>
</li>
</ul>
</div>
That is a better solution than generate the components in the service because just imagine you don't want buttons list but a togglebuttons list in your HCI, here you just have to change the HTML. The service is still the same, and the components typescipt part is still the same too !

Replacement for #helper in ASP.NET Core

So far, i don't think ViewComponent solves that neither does TagHelper. Is there any replacement to this? Something that takes parameters and returns a HtmlString?
I don't see anything harmful with:
#helper foo(string something) {
<div>Say #something</div>
}
var emailbody = classfilenameinAppCodefolder.foo("hello"); //store result in a variable for further processes
For now i believe its a temporary delete before RC. https://github.com/aspnet/Razor/issues/281 and https://github.com/aspnet/Mvc/issues/1130 Well! it better be. I hope someone is working on it. Without #helper, building large HtmlString or 'template' would be a serious pain.
Note: Partial View doesn't seem to do the trick. I think it only renders views not return view to variable.
Secondly, what happened to the App_Code folder?
According to the following Github issue, it looks like #helper is coming back and will be included in asp .net core 3.0.0 preview 4.
https://github.com/aspnet/AspNetCore/issues/5110
UPDATE
Starting in asp .net core 3, you can now define a local function within a Razor code block.
#{
void RenderName(string name)
{
<p>Name: <strong>#name</strong></p>
}
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#razor-code-blocks
Alternatively you can use the #functions directive like this:
#{
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}
#functions {
private void RenderName(string name)
{
<p>Name: <strong>#name</strong></p>
}
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#functions
#{
Func<String, IHtmlContent> foo = #<div>Say #item</div>;
}
I'd like to expand on #Alexaku's answer and show how I've implemented a helper like function. It's only useful on one specific page but it allows you to execute a piece of razor code multiple times with input parameters. The syntax is not great but I've found it very useful in the absence of razor's #helper function. First declare some kind of Dto that will contain the input parameters into the function.
#functions {
private class Dto
{
public string Data { get;set; }
}
}
Then declare the razor function. Note that the displayItem value can be multi-line and also note that you access the Dto variable using the #item.
#{
Func<Dto, IHtmlContent> displayItem = #<span>#item.Data</span>;
}
Then when you want to use the razor template you can call it like the following from anywhere in the page.
<div>
#displayItem(new Dto {Data = "testingData1" });
</div>
<div>
#displayItem(new Dto {Data = "testingData2" });
</div>
For .NET Core 3, you can use local functions:
#{
void RenderName(string name)
{
<p>Name: <strong>#name</strong></p>
}
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#razor-code-blocks
As #scott pointed out in his answer, local functions are finally available as of .NET Core 3. In prior versions one can resort to templated Razor delegates.
But none of the answers addresses the question "what happened to the App_Code folder?" The aforementioned features are local solutions, that is, helper functions defined in these ways cannot be shared between multiple views. But global helper functions could often be more convenient than the solutions MS provide out-of-the-box for view-related code re-use. (Tag helpers, partial views, view components all have their cons.) This was thoroughly discussed in this and this GitHub issue. According to these discourses, unfortunately, there's not much understanding from MS's side, so not much hope is left that this feature will be added any time soon, if ever.
However, after digging into the framework sources, I think, I could come up with a viable solution to the problem.
The core idea is that we can utilize the Razor view engine to look up an arbitrary view for us: e.g. a partial view which defines some local functions we want to use globally. Once we manage to get hold of a reference to this view, nothing prevents us from calling its public methods.
The GlobalRazorHelpersFactory class below encapsulates this idea:
public interface IGlobalRazorHelpersFactory
{
dynamic Create(string helpersViewPath, ViewContext viewContext);
THelpers Create<THelpers>(ViewContext viewContext) where THelpers : class;
}
public class GlobalRazorHelpersOptions
{
public Dictionary<Type, string> HelpersTypeViewPathMappings { get; } = new Dictionary<Type, string>();
}
public sealed class GlobalRazorHelpersFactory : IGlobalRazorHelpersFactory
{
private readonly ICompositeViewEngine _viewEngine;
private readonly IRazorPageActivator _razorPageActivator;
private readonly ConcurrentDictionary<Type, string> _helpersTypeViewPathMappings;
public GlobalRazorHelpersFactory(ICompositeViewEngine viewEngine, IRazorPageActivator razorPageActivator, IOptions<GlobalRazorHelpersOptions>? options)
{
_viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine));
_razorPageActivator = razorPageActivator ?? throw new ArgumentNullException(nameof(razorPageActivator));
var optionsValue = options?.Value;
_helpersTypeViewPathMappings = new ConcurrentDictionary<Type, string>(optionsValue?.HelpersTypeViewPathMappings ?? Enumerable.Empty<KeyValuePair<Type, string>>());
}
public IRazorPage CreateRazorPage(string helpersViewPath, ViewContext viewContext)
{
var viewEngineResult = _viewEngine.GetView(viewContext.ExecutingFilePath, helpersViewPath, isMainPage: false);
var originalLocations = viewEngineResult.SearchedLocations;
if (!viewEngineResult.Success)
viewEngineResult = _viewEngine.FindView(viewContext, helpersViewPath, isMainPage: false);
if (!viewEngineResult.Success)
{
var locations = string.Empty;
if (originalLocations.Any())
locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations);
if (viewEngineResult.SearchedLocations.Any())
locations += Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations);
throw new InvalidOperationException($"The Razor helpers view '{helpersViewPath}' was not found. The following locations were searched:{locations}");
}
var razorPage = ((RazorView)viewEngineResult.View).RazorPage;
razorPage.ViewContext = viewContext;
// we need to save and restore the original view data dictionary as it is changed by IRazorPageActivator.Activate
// https://github.com/dotnet/aspnetcore/blob/v3.1.6/src/Mvc/Mvc.Razor/src/RazorPagePropertyActivator.cs#L59
var originalViewData = viewContext.ViewData;
try { _razorPageActivator.Activate(razorPage, viewContext); }
finally { viewContext.ViewData = originalViewData; }
return razorPage;
}
public dynamic Create(string helpersViewPath, ViewContext viewContext) => CreateRazorPage(helpersViewPath, viewContext);
public THelpers Create<THelpers>(ViewContext viewContext) where THelpers : class
{
var helpersViewPath = _helpersTypeViewPathMappings.GetOrAdd(typeof(THelpers), type => "_" + (type.Name.StartsWith("I", StringComparison.Ordinal) ? type.Name.Substring(1) : type.Name));
return (THelpers)CreateRazorPage(helpersViewPath, viewContext);
}
}
After introducing the singleton IGlobalRazorHelpersFactory service to DI, we could inject it in views and call the Create method to acquire an instance of the view which contains our helper functions.
By using the #implements directive in the helper view, we can even get type-safe access:
#inherits Microsoft.AspNetCore.Mvc.Razor.RazorPage
#implements IMyGlobalHelpers
#functions {
public void MyAwesomeGlobalFunction(string someParam)
{
<div>#someParam</div>
}
}
(One can define the interface type to view path mappings explicitly by configuring the GlobalRazorHelpersOptions in the ordinary way - by services.Configure<GlobalRazorHelpersOptions>(o => ...) - but usually we can simply rely on the naming convention of the implementation: in the case of the IMyGlobalHelpers interface, it will look for a view named _MyGlobalHelpers.cshtml at the regular locations. Best to put it in /Views/Shared.)
Nice so far but we can do even better! It'd be much more convenient if we could inject the helper instance directly in the consumer view. We can easily achieve this using the ideas behind IOptions<T>/HtmlLocalizer<T>/ViewLocalizer:
public interface IGlobalRazorHelpers<out THelpers> : IViewContextAware
where THelpers : class
{
THelpers Instance { get; }
}
public sealed class GlobalRazorHelpers<THelpers> : IGlobalRazorHelpers<THelpers>
where THelpers : class
{
private readonly IGlobalRazorHelpersFactory _razorHelpersFactory;
public GlobalRazorHelpers(IGlobalRazorHelpersFactory razorHelpersFactory)
{
_razorHelpersFactory = razorHelpersFactory ?? throw new ArgumentNullException(nameof(razorHelpersFactory));
}
private THelpers? _instance;
public THelpers Instance => _instance ?? throw new InvalidOperationException("The service was not contextualized.");
public void Contextualize(ViewContext viewContext) => _instance = _razorHelpersFactory.Create<THelpers>(viewContext);
}
Now we have to register our services in Startup.ConfigureServices:
services.AddSingleton<IGlobalRazorHelpersFactory, GlobalRazorHelpersFactory>();
services.AddTransient(typeof(IGlobalRazorHelpers<>), typeof(GlobalRazorHelpers<>));
Finally, we're ready for consuming our global Razor functions in our views:
#inject IGlobalRazorHelpers<IMyGlobalHelpers> MyGlobalHelpers;
#{ MyGlobalHelpers.Instance.MyAwesomeGlobalFunction("Here we go!"); }
This is a bit more complicated than the original App_Code + static methods feature but I think this is the closest we can get. According to my tests, the solution also works nicely with runtime compilation enabled. I haven't had the time so far to do benchmarks but, in theory, it should generally be faster than using partial views as the shared view is looked up only once per consumer view and after that it's just plain method calls. I'm not sure about tag helpers though. It'd be interesting to do some benchmarks comparing them. But I leave that up to the adopter.
(Tested on .NET Core 3.1.)
Update
You can find a working demo of this concept in my ASP.NET boilerplate project:
Infrastructure (relevant files are only those whose name contains GlobalRazorHelpers)
Registration
Helper interface sample
Helper implementation sample
Usage sample
The #helper directive was removed since it was incomplete and its current design did not fit in the new 'ASP.NET 5 way'. One of the reasons is that helpers should be declared in the App_Code folder while ASP.NET 5 has no concept of special folders. Therefore the team decided to temporarily remove the feature.
There are plans to bring it back in the future though. See this and this.
You can easily replace that "feature" with a ViewComponent (and a TagHelper if you want). ASP.NET Core is much more friendly to web designers, and the ViewComponents allow you to write HTML without any (weird to most) razor code.
For example:
Create a SayComponent : ViewComponent class:
public class SayComponent : ViewComponent
{
public void Render(string message)
{
return View(message);
}
}
Create a View file under Views/Shared/Say/Default.cshtml with just
#model string
<div>Message: #Model.</div>
And call it:
#await Component.RenderAsync("Say", "some message")
For a better experience, add this to your _ViewImports.cshtml file:
#addTagHelper *, YourSolutionName
And then you can use it as a tag helper:
<vc:say message="some message"></vc:say>
How about using partials to recreate reusable tags?
MyProject/Views/Shared/_foo.cshtml
#model string
<div>#Model</div>
MyProject/Views/Courses/Index.cshtml
#{
Layout = "_Layout";
}
<div>
<partial name="_foo" model="foo" />
<partial name="_foo" model="bar" />
<partial name="_foo" model="baz" />
</div>