• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM UTC coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.32
/projects/igniteui-angular/src/lib/directives/template-outlet/template_outlet.directive.ts
1
import {
2
    Directive, EmbeddedViewRef, Input, OnChanges, ChangeDetectorRef,
3
    SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef, NgZone, Output, EventEmitter
4
} from '@angular/core';
5

6
import { IBaseEventArgs } from '../../core/utils';
7

8
/**
9
 * @hidden
10
 */
11
@Directive({
12
    selector: '[igxTemplateOutlet]',
13
    standalone: true
14
})
15
export class IgxTemplateOutletDirective implements OnChanges {
2✔
16
    @Input() public igxTemplateOutletContext !: any;
17

18
    @Input() public igxTemplateOutlet !: TemplateRef<any>;
19

20
    @Output()
21
    public viewCreated = new EventEmitter<IViewChangeEventArgs>();
265✔
22

23
    @Output()
24
    public viewMoved = new EventEmitter<IViewChangeEventArgs>();
265✔
25

26
    @Output()
27
    public cachedViewLoaded = new EventEmitter<ICachedViewLoadedEventArgs>();
265✔
28

29
    @Output()
30
    public beforeViewDetach = new EventEmitter<IViewChangeEventArgs>();
265✔
31

32
    private _viewRef !: EmbeddedViewRef<any>;
33

34
    /**
35
     * The embedded views cache. Collection is key-value paired.
36
     * Key is the template type, value is another key-value paired collection
37
     * where the key is the template id and value is the embedded view for the related template.
38
     */
39
    private _embeddedViewsMap: Map<string, Map<any, EmbeddedViewRef<any>>> = new Map();
265✔
40

41
    constructor(public _viewContainerRef: ViewContainerRef, private _zone: NgZone, public cdr: ChangeDetectorRef) {
265✔
42
    }
43

44
    public ngOnChanges(changes: SimpleChanges) {
45
        const actionType: TemplateOutletAction = this._getActionType(changes);
1,442✔
46
        switch (actionType) {
1,442!
47
            case TemplateOutletAction.CreateView: this._recreateView(); break;
265✔
UNCOV
48
            case TemplateOutletAction.MoveView: this._moveView(); break;
×
UNCOV
49
            case TemplateOutletAction.UseCachedView: this._useCachedView(); break;
×
50
            case TemplateOutletAction.UpdateViewContext: this._updateExistingContext(this.igxTemplateOutletContext); break;
1,177✔
51
        }
52
    }
53

54
    public cleanCache() {
55
        this._embeddedViewsMap.forEach((collection) => {
90✔
56
            collection.forEach((item => {
90✔
57
                if (!item.destroyed) {
90!
UNCOV
58
                    item.destroy();
×
59
                }
60
            }));
61
            collection.clear();
90✔
62
        });
63
        this._embeddedViewsMap.clear();
90✔
64
    }
65

66
    public cleanView(tmplID) {
UNCOV
67
        const embViewCollection = this._embeddedViewsMap.get(tmplID.type);
×
UNCOV
68
        const embView = embViewCollection?.get(tmplID.id);
×
UNCOV
69
        if (embView) {
×
UNCOV
70
            embView.destroy();
×
UNCOV
71
            this._embeddedViewsMap.get(tmplID.type).delete(tmplID.id);
×
72
        }
73
    }
74

75
    private _recreateView() {
76
        const prevIndex = this._viewRef ? this._viewContainerRef.indexOf(this._viewRef) : -1;
265!
77
        // detach old and create new
78
        if (prevIndex !== -1) {
265!
UNCOV
79
            this.beforeViewDetach.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext });
×
UNCOV
80
            this._viewContainerRef.detach(prevIndex);
×
81
        }
82
        if (this.igxTemplateOutlet) {
265✔
83
            this._viewRef = this._viewContainerRef.createEmbeddedView(
265✔
84
                this.igxTemplateOutlet, this.igxTemplateOutletContext);
85
            this.viewCreated.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext });
265✔
86
            const tmplId = this.igxTemplateOutletContext['templateID'];
265✔
87
            if (tmplId) {
265✔
88
                // if context contains a template id, check if we have a view for that template already stored in the cache
89
                // if not create a copy and add it to the cache in detached state.
90
                // Note: Views in detached state do not appear in the DOM, however they remain stored in memory.
91
                const resCollection = this._embeddedViewsMap.get(this.igxTemplateOutletContext['templateID'].type);
265✔
92
                const res = resCollection?.get(this.igxTemplateOutletContext['templateID'].id);
265✔
93
                if (!res) {
265✔
94
                    this._embeddedViewsMap.set(this.igxTemplateOutletContext['templateID'].type,
265✔
95
                        new Map([[this.igxTemplateOutletContext['templateID'].id, this._viewRef]]));
96
                }
97
            }
98
        }
99
    }
100

101
    private _moveView() {
102
        // using external view and inserting it in current view.
UNCOV
103
        const view = this.igxTemplateOutletContext['moveView'];
×
UNCOV
104
        const owner = this.igxTemplateOutletContext['owner'];
×
UNCOV
105
        if (view !== this._viewRef) {
×
UNCOV
106
            if (owner._viewContainerRef.indexOf(view) !== -1) {
×
107
                // detach in case view it is attached somewhere else at the moment.
UNCOV
108
                this.beforeViewDetach.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext });
×
UNCOV
109
                owner._viewContainerRef.detach(owner._viewContainerRef.indexOf(view));
×
110
            }
UNCOV
111
            if (this._viewRef && this._viewContainerRef.indexOf(this._viewRef) !== -1) {
×
UNCOV
112
                this.beforeViewDetach.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext });
×
UNCOV
113
                this._viewContainerRef.detach(this._viewContainerRef.indexOf(this._viewRef));
×
114
            }
UNCOV
115
            this._viewRef = view;
×
UNCOV
116
            this._viewContainerRef.insert(view, 0);
×
UNCOV
117
            this._updateExistingContext(this.igxTemplateOutletContext);
×
UNCOV
118
            this.viewMoved.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext });
×
119
        } else {
UNCOV
120
            this._updateExistingContext(this.igxTemplateOutletContext);
×
121
        }
122
    }
123
    private _useCachedView() {
124
        // use view for specific template cached in the current template outlet
UNCOV
125
        const tmplID = this.igxTemplateOutletContext['templateID'];
×
UNCOV
126
        const cachedView = tmplID ?
×
127
            this._embeddedViewsMap.get(tmplID.type)?.get(tmplID.id) :
128
            null;
129
        // if view exists, but template has been changed and there is a view in the cache with the related template
130
        // then detach old view and insert the stored one with the matching template
131
        // after that update its context.
UNCOV
132
        if (this._viewContainerRef.length > 0) {
×
UNCOV
133
            this.beforeViewDetach.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext });
×
UNCOV
134
            this._viewContainerRef.detach(this._viewContainerRef.indexOf(this._viewRef));
×
135
        }
136

UNCOV
137
        this._viewRef = cachedView;
×
UNCOV
138
        const oldContext = this._cloneContext(cachedView.context);
×
UNCOV
139
        this._viewContainerRef.insert(this._viewRef, 0);
×
UNCOV
140
        this._updateExistingContext(this.igxTemplateOutletContext);
×
UNCOV
141
        this.cachedViewLoaded.emit({ owner: this, view: this._viewRef, context: this.igxTemplateOutletContext, oldContext });
×
142
    }
143

144
    private _shouldRecreateView(changes: SimpleChanges): boolean {
145
        const ctxChange = changes['igxTemplateOutletContext'];
1,442✔
146
        return !!changes['igxTemplateOutlet'] || (ctxChange && this._hasContextShapeChanged(ctxChange));
1,442✔
147
    }
148

149
    private _hasContextShapeChanged(ctxChange: SimpleChange): boolean {
150
        const prevCtxKeys = Object.keys(ctxChange.previousValue || {});
1,177!
151
        const currCtxKeys = Object.keys(ctxChange.currentValue || {});
1,177!
152

153
        if (prevCtxKeys.length === currCtxKeys.length) {
1,177!
154
            for (const propName of currCtxKeys) {
1,177✔
155
                if (prevCtxKeys.indexOf(propName) === -1) {
4,708!
156
                    return true;
×
157
                }
158
            }
159
            return false;
1,177✔
160
        } else {
UNCOV
161
            return true;
×
162
        }
163
    }
164

165
    private _updateExistingContext(ctx: any): void {
166
        for (const propName of Object.keys(ctx)) {
1,177✔
167
            this._viewRef.context[propName] = this.igxTemplateOutletContext[propName];
4,708✔
168
        }
169
    }
170

171
    private _cloneContext(ctx: any): any {
UNCOV
172
        const clone = {};
×
UNCOV
173
        for (const propName of Object.keys(ctx)) {
×
UNCOV
174
            clone[propName] = ctx[propName];
×
175
        }
UNCOV
176
        return clone;
×
177
    }
178

179
    private _getActionType(changes: SimpleChanges) {
180
        const movedView = this.igxTemplateOutletContext['moveView'];
1,442✔
181
        const tmplID = this.igxTemplateOutletContext['templateID'];
1,442✔
182
        const cachedView = tmplID ?
1,442!
183
            this._embeddedViewsMap.get(tmplID.type)?.get(tmplID.id) :
184
            null;
185
        const shouldRecreate = this._shouldRecreateView(changes);
1,442✔
186
        if (movedView) {
1,442!
187
            // view is moved from external source
UNCOV
188
            return TemplateOutletAction.MoveView;
×
189
        } else if (shouldRecreate && cachedView) {
1,442!
190
            // should recreate (template or context change) and there is a matching template in cache
UNCOV
191
            return TemplateOutletAction.UseCachedView;
×
192
        } else if (!this._viewRef || shouldRecreate) {
1,442✔
193
            // no view or should recreate
194
            return TemplateOutletAction.CreateView;
265✔
195
        } else if (this.igxTemplateOutletContext) {
1,177✔
196
            // has context, update context
197
            return TemplateOutletAction.UpdateViewContext;
1,177✔
198
        }
199
    }
200
}
201
enum TemplateOutletAction {
2✔
202
    CreateView,
2✔
203
    MoveView,
2✔
204
    UseCachedView,
2✔
205
    UpdateViewContext
2✔
206
}
207

208
export interface IViewChangeEventArgs extends IBaseEventArgs {
209
    owner: IgxTemplateOutletDirective;
210
    view: EmbeddedViewRef<any>;
211
    context: any;
212
}
213

214
export interface ICachedViewLoadedEventArgs extends IViewChangeEventArgs {
215
    oldContext: any;
216
}
217

218
/**
219
 * @hidden
220
 */
221

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc