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

eclipsesource / jsonforms / 10852566272

13 Sep 2024 04:16PM UTC coverage: 81.733% (-1.6%) from 83.285%
10852566272

push

github

web-flow
fix(translations): memoize array translation (#2358)

This commit addresses an issue where array translations were created as new objects within the core module on each render cycle, causing unnecessary rerenders. By memoizing the translation object in the material renderer set, this commit prevents redundant rerenders.
This commit also adds memorization to the attributes of the material oneOfRenderer to further avoid unnecessary rerendering.

Co-authored-by: Stefan Dirix <sdirix@eclipsesource.com>

10158 of 23385 branches covered (43.44%)

20 of 30 new or added lines in 11 files covered. (66.67%)

1059 existing lines in 47 files now uncovered.

17267 of 21126 relevant lines covered (81.73%)

27.21 hits per line

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

7.41
/packages/angular-material/src/library/layouts/array-layout.renderer.ts
1
/*
2
  The MIT License
3

4
  Copyright (c) 2017-2020 EclipseSource Munich
5
  https://github.com/eclipsesource/jsonforms
6

7
  Permission is hereby granted, free of charge, to any person obtaining a copy
8
  of this software and associated documentation files (the "Software"), to deal
9
  in the Software without restriction, including without limitation the rights
10
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
  copies of the Software, and to permit persons to whom the Software is
12
  furnished to do so, subject to the following conditions:
13

14
  The above copyright notice and this permission notice shall be included in
15
  all copies or substantial portions of the Software.
16

17
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
  THE SOFTWARE.
24
*/
25
import {
26
  ChangeDetectionStrategy,
27
  Component,
28
  OnDestroy,
29
  OnInit,
30
} from '@angular/core';
31
import {
32
  JsonFormsAngularService,
33
  JsonFormsAbstractControl,
34
} from '@jsonforms/angular';
35
import {
36
  arrayDefaultTranslations,
37
  ArrayLayoutProps,
38
  ArrayTranslations,
39
  createDefaultValue,
40
  defaultJsonFormsI18nState,
41
  findUISchema,
42
  getArrayTranslations,
43
  isObjectArrayWithNesting,
44
  JsonFormsState,
45
  mapDispatchToArrayControlProps,
46
  mapStateToArrayLayoutProps,
47
  OwnPropsOfRenderer,
48
  Paths,
49
  RankedTester,
50
  rankWith,
51
  setReadonly,
52
  StatePropsOfArrayLayout,
53
  UISchemaElement,
54
  UISchemaTester,
55
  unsetReadonly,
56
} from '@jsonforms/core';
57

58
@Component({
59
  selector: 'app-array-layout-renderer',
60
  template: `
61
    <div [ngStyle]="{ display: hidden ? 'none' : '' }" class="array-layout">
62
      <div class="array-layout-toolbar">
63
        <h2 class="mat-h2 array-layout-title">{{ label }}</h2>
64
        <span></span>
65
        <mat-icon
66
          *ngIf="this.error?.length"
67
          color="warn"
68
          matBadge="{{ this.error.split('').length }}"
69
          matBadgeColor="warn"
70
          matTooltip="{{ this.error }}"
71
          matTooltipClass="error-message-tooltip"
72
        >
73
          error_outline
74
        </mat-icon>
75
        <span></span>
76
        <button
77
          mat-button
78
          matTooltip="{{ translations.addTooltip }}"
79
          [disabled]="!isEnabled()"
80
          (click)="add()"
81
          attr.aria-label="{{ translations.addAriaLabel }}"
82
        >
83
          <mat-icon>add</mat-icon>
84
        </button>
85
      </div>
86
      <p *ngIf="noData">{{ translations.noDataMessage }}</p>
87
      <div
88
        *ngFor="
89
          let item of [].constructor(data);
90
          let idx = index;
91
          trackBy: trackByFn;
92
          last as last;
93
          first as first
94
        "
95
      >
96
        <mat-card class="array-item" appearance="outlined">
97
          <mat-card-content>
98
            <jsonforms-outlet [renderProps]="getProps(idx)"></jsonforms-outlet>
99
          </mat-card-content>
100
          <mat-card-actions *ngIf="isEnabled()">
101
            <button
102
              *ngIf="uischema?.options?.showSortButtons"
103
              class="item-up"
104
              mat-button
105
              [disabled]="first"
106
              (click)="up(idx)"
107
              attr.aria-label="{{ translations.upAriaLabel }}"
108
              matTooltip="{{ translations.up }}"
109
              matTooltipPosition="right"
110
            >
111
              <mat-icon>arrow_upward</mat-icon>
112
            </button>
113
            <button
114
              *ngIf="uischema?.options?.showSortButtons"
115
              class="item-down"
116
              mat-button
117
              [disabled]="last"
118
              (click)="down(idx)"
119
              attr.aria-label="{{ translations.downAriaLabel }}"
120
              matTooltip="{{ translations.down }}"
121
              matTooltipPosition="right"
122
            >
123
              <mat-icon>arrow_downward</mat-icon>
124
            </button>
125
            <button
126
              mat-button
127
              color="warn"
128
              (click)="remove(idx)"
129
              attr.aria-label="{{ translations.removeAriaLabel }}"
130
              matTooltip="{{ translations.removeTooltip }}"
131
              matTooltipPosition="right"
132
            >
133
              <mat-icon>delete</mat-icon>
134
            </button>
135
          </mat-card-actions>
136
        </mat-card>
137
      </div>
138
    </div>
139
  `,
140
  styles: [
141
    `
142
      .array-layout {
143
        display: flex;
144
        flex-direction: column;
145
        gap: 16px;
146
      }
147
      .array-layout > * {
148
        flex: 1 1 auto;
149
      }
150
      .array-layout-toolbar {
151
        display: flex;
152
        align-items: center;
153
      }
154
      .array-layout-title {
155
        margin: 0;
156
      }
157
      .array-layout-toolbar > span {
158
        flex: 1 1 auto;
159
      }
160
      .array-item {
161
        padding: 16px;
162
      }
163
      ::ng-deep .error-message-tooltip {
164
        white-space: pre-line;
165
      }
166
    `,
167
  ],
168
  changeDetection: ChangeDetectionStrategy.OnPush,
169
})
170
export class ArrayLayoutRenderer
1✔
171
  extends JsonFormsAbstractControl<StatePropsOfArrayLayout>
172
  implements OnInit, OnDestroy
173
{
174
  noData: boolean;
NEW
175
  translations: ArrayTranslations = {};
×
176
  addItem: (path: string, value: any) => () => void;
177
  moveItemUp: (path: string, index: number) => () => void;
178
  moveItemDown: (path: string, index: number) => () => void;
179
  removeItems: (path: string, toDelete: number[]) => () => void;
180
  uischemas: {
181
    tester: UISchemaTester;
182
    uischema: UISchemaElement;
183
  }[];
184
  constructor(jsonFormsService: JsonFormsAngularService) {
185
    super(jsonFormsService);
×
186
  }
187
  mapToProps(
188
    state: JsonFormsState
189
  ): StatePropsOfArrayLayout & { translations: ArrayTranslations } {
190
    const props = mapStateToArrayLayoutProps(state, this.getOwnProps());
×
191
    const t =
NEW
192
      state.jsonforms.i18n?.translate ?? defaultJsonFormsI18nState.translate;
×
NEW
193
    const translations = getArrayTranslations(
×
194
      t,
195
      arrayDefaultTranslations,
196
      props.i18nKeyPrefix,
197
      props.label
198
    );
NEW
199
    return { ...props, translations };
×
200
  }
201
  remove(index: number): void {
202
    this.removeItems(this.propsPath, [index])();
×
203
  }
204
  add(): void {
205
    this.addItem(
×
206
      this.propsPath,
207
      createDefaultValue(this.scopedSchema, this.rootSchema)
208
    )();
209
  }
210
  up(index: number): void {
211
    this.moveItemUp(this.propsPath, index)();
×
212
  }
213
  down(index: number): void {
214
    this.moveItemDown(this.propsPath, index)();
×
215
  }
216
  ngOnInit() {
217
    super.ngOnInit();
×
218
    const { addItem, removeItems, moveUp, moveDown } =
219
      mapDispatchToArrayControlProps(
×
220
        this.jsonFormsService.updateCore.bind(this.jsonFormsService)
221
      );
222
    this.addItem = addItem;
×
223
    this.moveItemUp = moveUp;
×
224
    this.moveItemDown = moveDown;
×
225
    this.removeItems = removeItems;
×
226
  }
227
  mapAdditionalProps(
228
    props: ArrayLayoutProps & { translations: ArrayTranslations }
229
  ) {
230
    this.noData = !props.data || props.data === 0;
×
231
    this.uischemas = props.uischemas;
×
NEW
232
    this.translations = props.translations;
×
233
  }
234
  getProps(index: number): OwnPropsOfRenderer {
235
    const uischema = findUISchema(
×
236
      this.uischemas,
237
      this.scopedSchema,
238
      this.uischema.scope,
239
      this.propsPath,
240
      undefined,
241
      this.uischema,
242
      this.rootSchema
243
    );
244
    if (this.isEnabled()) {
×
245
      unsetReadonly(uischema);
×
246
    } else {
247
      setReadonly(uischema);
×
248
    }
249
    return {
×
250
      schema: this.scopedSchema,
251
      path: Paths.compose(this.propsPath, `${index}`),
252
      uischema,
253
    };
254
  }
255
  trackByFn(index: number) {
256
    return index;
×
257
  }
258
}
259

260
export const ArrayLayoutRendererTester: RankedTester = rankWith(
1✔
261
  4,
262
  isObjectArrayWithNesting
263
);
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

© 2026 Coveralls, Inc