-
Notifications
You must be signed in to change notification settings - Fork 227
/
Copy pathcloudinary-image.component.ts
194 lines (172 loc) · 6.2 KB
/
cloudinary-image.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import {
Component,
ElementRef,
EventEmitter,
Input,
Output,
ContentChildren,
QueryList,
AfterViewInit,
AfterContentChecked,
OnInit,
OnChanges,
SimpleChanges,
OnDestroy,
ContentChild,
Renderer2,
} from '@angular/core';
import { Cloudinary } from './cloudinary.service';
import { CloudinaryTransformationDirective } from './cloudinary-transformation.directive';
import { CloudinaryPlaceHolder } from './cloudinary-placeholder.component';
import { isBrowser } from './cloudinary.service';
import { SDKAnalyticsConstants } from './SDKAnalyticsConstants';
@Component({
selector: 'cl-image',
template: `<img (load)="hasLoaded()">
<div *ngIf="placeholderComponent && shouldShowPlaceHolder" [style.display]="shouldShowPlaceHolder ? 'inline' : 'none'">
<ng-content></ng-content>
</div>
`,
})
export class CloudinaryImage
implements AfterViewInit, OnInit, AfterViewInit, AfterContentChecked, OnChanges, OnDestroy {
@Input('public-id') publicId: string;
@Input('client-hints') clientHints?: boolean;
@Input('loading') loading: string;
@Input('width') width?: string;
@Input('height') height?: string;
@Input('accessibility') accessibility?: string;
@ContentChildren(CloudinaryTransformationDirective)
transformations: QueryList<CloudinaryTransformationDirective>;
@ContentChild(CloudinaryPlaceHolder) placeholderComponent: CloudinaryPlaceHolder;
@Output() onLoad: EventEmitter<boolean> = new EventEmitter(); // Callback when an image is loaded successfully
@Output() onError: EventEmitter<boolean> = new EventEmitter(); // Callback when an image is loaded with error
observer: MutationObserver;
shouldShowPlaceHolder = true;
options: object = {};
constructor(private el: ElementRef, private cloudinary: Cloudinary, private renderer: Renderer2) {}
ngOnInit(): void {
if (isBrowser()) {
// Create an observer instance
this.observer = new MutationObserver(() => {
this.loadImage();
});
// Observe changes to attributes or child transformations to re-render the image
const config = { attributes: true, childList: true };
// pass in the target node, as well as the observer options
this.observer.observe(this.el.nativeElement, config);
}
}
ngOnChanges(changes: SimpleChanges) {
// Listen to changes on the data-bound property 'publicId'.
// Update component unless this is the first value assigned.
if (changes.publicId && !changes.publicId.isFirstChange()) {
this.loadImage();
}
}
ngOnDestroy(): void {
if (this.observer && this.observer.disconnect) {
this.observer.disconnect();
}
}
ngAfterViewInit() {
this.loadImage();
}
ngAfterContentChecked() {
if (this.width && this.placeholderComponent) {
this.placeholderComponent.setWidth(this.width);
}
if (this.height && this.placeholderComponent) {
this.placeholderComponent.setHeight(this.height);
}
if (this.placeholderComponent) {
this.placeholderComponent.setPublicId(this.publicId);
}
}
/**
* appends opacity and position to cl-img->img when placeholder is displayed
* removes styling from cl-img->img when placeholder does not display
*/
setPlaceHolderStyle() {
if (this.shouldShowPlaceHolder) {
this.renderer.setStyle(this.el.nativeElement.children[0], 'opacity', '0' );
this.renderer.setStyle(this.el.nativeElement.children[0], 'position', 'absolute' );
} else {
// note this only removes styling from cl-img->img and not cl-img
this.renderer.removeAttribute(this.el.nativeElement.children[0], 'style');
}
}
hasLoaded() {
this.shouldShowPlaceHolder = false;
}
loadImage() {
// https://github.com/angular/universal#universal-gotchas
// Fetch the image only for client side rendering by the browser
if (isBrowser()) {
if (!this.publicId) {
throw new Error(
'You must set the public id of the image to load, e.g. <cl-image public-id={{photo.public_id}}...></cl-image>'
);
}
const nativeElement = this.el.nativeElement;
const image = nativeElement.children[0];
// Add onload and onerror handlers
image.onload = e => {
this.onLoad.emit(e);
};
image.onerror = e => {
this.onError.emit(e);
};
this.options = this.cloudinary.toCloudinaryAttributes(
nativeElement.attributes,
this.transformations
);
if (this.clientHints || (typeof this.clientHints === 'undefined' && this.cloudinary.config().client_hints)) {
delete this.options['class'];
delete this.options['data-src'];
delete this.options['responsive'];
}
if (this.cloudinary.config().urlAnalytics) {
this.options = {...SDKAnalyticsConstants, ...this.options};
}
if (this.placeholderComponent) {
this.placeholderHandler(this.options, image);
}
if (this.accessibility) {
this.options['src'] = this.accessibilityModeHandler();
}
const imageTag = this.cloudinary.imageTag(this.publicId, this.options);
this.setElementAttributes(image, imageTag.attributes());
if (this.options['responsive']) {
this.cloudinary.responsive(image, this.options);
}
}
}
setElementAttributes(element, attributesLiteral) {
Object.keys(attributesLiteral).forEach(attrName => {
const attr = attrName === 'src' && this.loading === 'lazy' ? 'data-src' : attrName;
this.renderer.setAttribute(element, attr, attributesLiteral[attrName]);
});
// Enforcing placeholder style
if (this.placeholderComponent) {
this.setPlaceHolderStyle();
}
}
/**
* Handles placeholder options
* In case of responsive sets width from resize
* In case width or height is known takes 10% of original dimension
*/
placeholderHandler(options, image) {
const placeholderOptions = { ...options };
if (placeholderOptions['width']) {
if (placeholderOptions['width'] === 'auto') {
placeholderOptions['width'] = image.getAttribute('data-width');
}
}
this.placeholderComponent.options = placeholderOptions;
}
accessibilityModeHandler() {
return this.cloudinary.url(this.publicId, {accessibility: this.accessibility, ...this.options});
}
}